@mastra/clickhouse 1.25.0 → 1.25.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/integrations-databases-clickhouse.md +1 -1
- package/dist/index.cjs +460 -191
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +461 -192
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/ddl.d.ts +3 -3
- package/dist/storage/domains/observability/v-next/ddl.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/helpers.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/preview-compat.d.ts +45 -0
- package/dist/storage/domains/observability/v-next/preview-compat.d.ts.map +1 -0
- package/dist/storage/domains/observability/v-next/previews.d.ts +16 -0
- package/dist/storage/domains/observability/v-next/previews.d.ts.map +1 -0
- package/dist/storage/domains/observability/v-next/span-query.d.ts +2 -0
- package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +66 -11
- 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 +2 -2
- package/dist/storage/domains/observability/v-next/trace-roots.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TupleParam, createClient } from "@clickhouse/client";
|
|
2
2
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
3
3
|
import * as coreStorage from "@mastra/core/storage";
|
|
4
|
-
import { BRANCH_SPAN_TYPES, EntityType, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, ObservabilityStorage, SCORERS_SCHEMA, SPAN_SCHEMA, ScoresStorage, TABLE_AGENT_VERSIONS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_TRACES, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, TraceStatus, WorkflowsStorage,
|
|
4
|
+
import { BRANCH_SPAN_TYPES, EntityType, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, ObservabilityStorage, SCORERS_SCHEMA, SPAN_SCHEMA, ScoresStorage, TABLE_AGENT_VERSIONS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_TRACES, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, TraceStatus, WorkflowsStorage, calculatePagination, computeTraceStatus, createStorageErrorId, getDefaultValue, getSqlType, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, normalizePerPage, safelyParseJSON, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
5
5
|
import { MessageList } from "@mastra/core/agent";
|
|
6
6
|
import { MastraBase } from "@mastra/core/base";
|
|
7
7
|
import { parseFieldKey } from "@mastra/core/utils";
|
|
@@ -942,12 +942,23 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
|
|
|
942
942
|
query_params: { messageIds }
|
|
943
943
|
});
|
|
944
944
|
if (threadIds.length > 0) {
|
|
945
|
-
const now =
|
|
946
|
-
await this.
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
945
|
+
const now = Date.now();
|
|
946
|
+
const existingThreads = await this.getLatestThreads(threadIds);
|
|
947
|
+
if (existingThreads.size > 0) await this.client.insert({
|
|
948
|
+
table: TABLE_THREADS,
|
|
949
|
+
format: "JSONEachRow",
|
|
950
|
+
values: Array.from(existingThreads.values()).map((existingThread) => ({
|
|
951
|
+
id: existingThread.id,
|
|
952
|
+
resourceId: existingThread.resourceId,
|
|
953
|
+
title: existingThread.title,
|
|
954
|
+
metadata: serializeMetadata(existingThread.metadata),
|
|
955
|
+
createdAt: existingThread.createdAt,
|
|
956
|
+
updatedAt: new Date(Math.max(now, new Date(existingThread.updatedAt).getTime() + 1)).toISOString().replace("Z", "")
|
|
957
|
+
})),
|
|
958
|
+
clickhouse_settings: {
|
|
959
|
+
date_time_input_format: "best_effort",
|
|
960
|
+
use_client_time_zone: 1,
|
|
961
|
+
output_format_json_quote_64bit_integers: 0
|
|
951
962
|
}
|
|
952
963
|
});
|
|
953
964
|
}
|
|
@@ -2722,7 +2733,14 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SPAN_EVENTS} (
|
|
|
2722
2733
|
output Nullable(String),
|
|
2723
2734
|
error Nullable(String),
|
|
2724
2735
|
metadataRaw Nullable(String),
|
|
2725
|
-
requestContext Nullable(String)
|
|
2736
|
+
requestContext Nullable(String),
|
|
2737
|
+
|
|
2738
|
+
-- Previews of \`input\` / \`output\` / \`error\` in core's \`buildPreview\` format, written
|
|
2739
|
+
-- on every span ('' when there is nothing to preview). NULL on rows written before
|
|
2740
|
+
-- the columns existed; readers build those from the payload columns.
|
|
2741
|
+
inputPreview Nullable(String),
|
|
2742
|
+
outputPreview Nullable(String),
|
|
2743
|
+
errorPreview Nullable(String)
|
|
2726
2744
|
)
|
|
2727
2745
|
ENGINE = ReplacingMergeTree
|
|
2728
2746
|
PARTITION BY toDate(endedAt)
|
|
@@ -2790,6 +2808,12 @@ CREATE TABLE IF NOT EXISTS ${TABLE_TRACE_ROOTS} (
|
|
|
2790
2808
|
metadataRaw Nullable(String),
|
|
2791
2809
|
requestContext Nullable(String),
|
|
2792
2810
|
|
|
2811
|
+
-- Previews of \`input\` / \`output\` / \`error\` ('' when there is nothing to preview).
|
|
2812
|
+
-- NULL on rows written before the columns existed.
|
|
2813
|
+
inputPreview Nullable(String),
|
|
2814
|
+
outputPreview Nullable(String),
|
|
2815
|
+
errorPreview Nullable(String),
|
|
2816
|
+
|
|
2793
2817
|
-- traceId is second in the sort key, behind startedAt, so point lookups by
|
|
2794
2818
|
-- traceId (getRootSpan, trace deletes, trace-query root dedupe) need a skip
|
|
2795
2819
|
-- index to avoid scanning every granule.
|
|
@@ -2868,6 +2892,9 @@ CREATE TABLE IF NOT EXISTS ${TABLE_TRACE_BRANCHES} (
|
|
|
2868
2892
|
error Nullable(String),
|
|
2869
2893
|
metadataRaw Nullable(String),
|
|
2870
2894
|
requestContext Nullable(String),
|
|
2895
|
+
inputPreview Nullable(String),
|
|
2896
|
+
outputPreview Nullable(String),
|
|
2897
|
+
errorPreview Nullable(String),
|
|
2871
2898
|
|
|
2872
2899
|
INDEX idx_traceId traceId TYPE bloom_filter(0.01) GRANULARITY 1
|
|
2873
2900
|
)
|
|
@@ -3599,7 +3626,26 @@ const addBloomIndex = (table, name, column, granularity = 2) => ({
|
|
|
3599
3626
|
name,
|
|
3600
3627
|
sql: `ALTER TABLE ${table} ADD INDEX IF NOT EXISTS ${name} ${column} TYPE bloom_filter(0.01) GRANULARITY ${granularity}`
|
|
3601
3628
|
});
|
|
3629
|
+
const LOG_SKIP_INDEX_COLUMNS = [
|
|
3630
|
+
"traceId",
|
|
3631
|
+
"threadId",
|
|
3632
|
+
"resourceId",
|
|
3633
|
+
"userId",
|
|
3634
|
+
"organizationId",
|
|
3635
|
+
"experimentId",
|
|
3636
|
+
"runId",
|
|
3637
|
+
"sessionId",
|
|
3638
|
+
"requestId"
|
|
3639
|
+
];
|
|
3602
3640
|
const ALL_MIGRATIONS = [
|
|
3641
|
+
...[
|
|
3642
|
+
"inputPreview",
|
|
3643
|
+
"outputPreview",
|
|
3644
|
+
"errorPreview"
|
|
3645
|
+
].flatMap((column) => [addColumn(TABLE_TRACE_ROOTS, column, "Nullable(String)"), addColumn(TABLE_TRACE_BRANCHES, column, "Nullable(String)")]),
|
|
3646
|
+
addColumn(TABLE_SPAN_EVENTS, "inputPreview", "Nullable(String)"),
|
|
3647
|
+
addColumn(TABLE_SPAN_EVENTS, "outputPreview", "Nullable(String)"),
|
|
3648
|
+
addColumn(TABLE_SPAN_EVENTS, "errorPreview", "Nullable(String)"),
|
|
3603
3649
|
addColumn(TABLE_SPAN_EVENTS, "entityVersionId", "Nullable(String)"),
|
|
3604
3650
|
addColumn(TABLE_SPAN_EVENTS, "parentEntityVersionId", "Nullable(String)"),
|
|
3605
3651
|
addColumn(TABLE_SPAN_EVENTS, "rootEntityVersionId", "Nullable(String)"),
|
|
@@ -3636,17 +3682,7 @@ const ALL_MIGRATIONS = [
|
|
|
3636
3682
|
addBloomIndex(TABLE_METRIC_EVENTS, "idx_runId", "runId"),
|
|
3637
3683
|
addBloomIndex(TABLE_METRIC_EVENTS, "idx_sessionId", "sessionId"),
|
|
3638
3684
|
addBloomIndex(TABLE_METRIC_EVENTS, "idx_requestId", "requestId"),
|
|
3639
|
-
...
|
|
3640
|
-
"traceId",
|
|
3641
|
-
"threadId",
|
|
3642
|
-
"resourceId",
|
|
3643
|
-
"userId",
|
|
3644
|
-
"organizationId",
|
|
3645
|
-
"experimentId",
|
|
3646
|
-
"runId",
|
|
3647
|
-
"sessionId",
|
|
3648
|
-
"requestId"
|
|
3649
|
-
].map((column) => addBloomIndex(TABLE_LOG_EVENTS, `idx_${column}`, column)),
|
|
3685
|
+
...LOG_SKIP_INDEX_COLUMNS.map((column) => addBloomIndex(TABLE_LOG_EVENTS, `idx_${column}`, column)),
|
|
3650
3686
|
addBloomIndex(TABLE_DELETION_REQUESTS, "idx_predicateValues", "predicateValues")
|
|
3651
3687
|
];
|
|
3652
3688
|
const ALL_TABLE_NAMES = [
|
|
@@ -3756,6 +3792,78 @@ function parseTtlExpression(expr) {
|
|
|
3756
3792
|
};
|
|
3757
3793
|
}
|
|
3758
3794
|
//#endregion
|
|
3795
|
+
//#region src/storage/domains/observability/v-next/preview-compat.ts
|
|
3796
|
+
const generator = coreStorage.buildStoredPreview;
|
|
3797
|
+
const canGeneratePreviews = typeof generator === "function";
|
|
3798
|
+
function needsPreview(value) {
|
|
3799
|
+
return canGeneratePreviews && value == null;
|
|
3800
|
+
}
|
|
3801
|
+
function storedPreview(preview, value, serialized) {
|
|
3802
|
+
return canGeneratePreviews ? generator(preview, value, serialized) : preview ?? null;
|
|
3803
|
+
}
|
|
3804
|
+
function readPreview(preview, payload, jsonEncoded = false) {
|
|
3805
|
+
const supplied = preview;
|
|
3806
|
+
if (!canGeneratePreviews) return supplied ?? null;
|
|
3807
|
+
if (supplied != null) return supplied.trim() || null;
|
|
3808
|
+
let value = payload;
|
|
3809
|
+
if (needsPreview(preview) && jsonEncoded && typeof payload === "string") try {
|
|
3810
|
+
value = JSON.parse(payload);
|
|
3811
|
+
} catch {
|
|
3812
|
+
value = null;
|
|
3813
|
+
}
|
|
3814
|
+
return storedPreview(supplied, value, jsonEncoded && typeof payload === "string" ? payload : void 0) || null;
|
|
3815
|
+
}
|
|
3816
|
+
/** Older response builders may shorten previews or turn empty strings into null. */
|
|
3817
|
+
function buildSpanQueryResponse(...args) {
|
|
3818
|
+
if (canGeneratePreviews) return coreStorage.buildSpanQueryResponse(...args);
|
|
3819
|
+
const [plan, selected, payloads, metrics] = args;
|
|
3820
|
+
const response = coreStorage.buildSpanQueryResponse(plan, selected, [], metrics);
|
|
3821
|
+
const byIdentity = new Map(payloads.map((payload) => [coreStorage.spanQueryIdentityKey(payload), payload]));
|
|
3822
|
+
for (const span of response.spans) {
|
|
3823
|
+
const payload = byIdentity.get(coreStorage.spanQueryIdentityKey(span));
|
|
3824
|
+
span.inputPreview = payload?.inputPreview ?? null;
|
|
3825
|
+
span.outputPreview = payload?.outputPreview ?? null;
|
|
3826
|
+
span.errorPreview = payload?.errorPreview ?? null;
|
|
3827
|
+
}
|
|
3828
|
+
return response;
|
|
3829
|
+
}
|
|
3830
|
+
//#endregion
|
|
3831
|
+
//#region src/storage/domains/observability/v-next/previews.ts
|
|
3832
|
+
/**
|
|
3833
|
+
* Stored span previews (`inputPreview` / `outputPreview` / `errorPreview`).
|
|
3834
|
+
*
|
|
3835
|
+
* The writer stores each preview in core's `buildPreview` format, `''` when there is nothing to
|
|
3836
|
+
* preview. A NULL preview means the row was written before the columns existed; readers fetch the
|
|
3837
|
+
* payload columns for just those rows and build the same preview from them.
|
|
3838
|
+
*/
|
|
3839
|
+
const PREVIEW_COLUMNS = [
|
|
3840
|
+
"inputPreview",
|
|
3841
|
+
"outputPreview",
|
|
3842
|
+
"errorPreview"
|
|
3843
|
+
];
|
|
3844
|
+
/** Payload column each preview is built from. */
|
|
3845
|
+
const PREVIEW_PAYLOAD_COLUMNS = {
|
|
3846
|
+
inputPreview: "input",
|
|
3847
|
+
outputPreview: "output",
|
|
3848
|
+
errorPreview: "error"
|
|
3849
|
+
};
|
|
3850
|
+
/** Whether a row was written before previews were stored, so it needs its payload columns. */
|
|
3851
|
+
function needsPreviewPayloads(row) {
|
|
3852
|
+
return PREVIEW_COLUMNS.some((column) => needsPreview(row[column]));
|
|
3853
|
+
}
|
|
3854
|
+
/**
|
|
3855
|
+
* The previews a list returns for a row: the stored ones (`''` becomes `null`), or for a row written
|
|
3856
|
+
* before they were stored, built from the payload columns on `payloads` (default: the row itself).
|
|
3857
|
+
*/
|
|
3858
|
+
function resolveStoredPreviews(row, payloads = row) {
|
|
3859
|
+
const previews = {};
|
|
3860
|
+
for (const column of PREVIEW_COLUMNS) {
|
|
3861
|
+
const stored = row[column];
|
|
3862
|
+
previews[column] = readPreview(stored, payloads?.[PREVIEW_PAYLOAD_COLUMNS[column]], true);
|
|
3863
|
+
}
|
|
3864
|
+
return previews;
|
|
3865
|
+
}
|
|
3866
|
+
//#endregion
|
|
3759
3867
|
//#region src/storage/domains/observability/v-next/review-status.ts
|
|
3760
3868
|
const FEEDBACK_REVIEW_STATUSES = ["needs-review", "reviewed"];
|
|
3761
3869
|
function isFeedbackReviewStatus(value) {
|
|
@@ -3962,7 +4070,7 @@ function rowToLightSpanRecord(row) {
|
|
|
3962
4070
|
endedAt
|
|
3963
4071
|
}),
|
|
3964
4072
|
metadata: parseJson(row.metadataRaw) ?? void 0,
|
|
3965
|
-
|
|
4073
|
+
...resolveStoredPreviews(row),
|
|
3966
4074
|
createdAt: startedAt,
|
|
3967
4075
|
updatedAt: null
|
|
3968
4076
|
};
|
|
@@ -3970,6 +4078,9 @@ function rowToLightSpanRecord(row) {
|
|
|
3970
4078
|
function spanRecordToRow(span) {
|
|
3971
4079
|
const endedAt = span.isEvent ? span.startedAt : span.endedAt ?? span.startedAt;
|
|
3972
4080
|
const metadata = span.metadata ?? null;
|
|
4081
|
+
const input = jsonEncode(span.input);
|
|
4082
|
+
const output = jsonEncode(span.output);
|
|
4083
|
+
const error = jsonEncode(span.error);
|
|
3973
4084
|
return {
|
|
3974
4085
|
dedupeKey: buildDedupeKey(span.traceId, span.spanId),
|
|
3975
4086
|
traceId: span.traceId,
|
|
@@ -4009,10 +4120,13 @@ function spanRecordToRow(span) {
|
|
|
4009
4120
|
scope: jsonEncode(span.scope),
|
|
4010
4121
|
attributes: jsonEncode(span.attributes),
|
|
4011
4122
|
links: jsonEncode(span.links),
|
|
4012
|
-
input
|
|
4013
|
-
output
|
|
4014
|
-
error
|
|
4015
|
-
requestContext: jsonEncode(span.requestContext)
|
|
4123
|
+
input,
|
|
4124
|
+
output,
|
|
4125
|
+
error,
|
|
4126
|
+
requestContext: jsonEncode(span.requestContext),
|
|
4127
|
+
inputPreview: storedPreview(span.inputPreview, span.input, input),
|
|
4128
|
+
outputPreview: storedPreview(span.outputPreview, span.output, output),
|
|
4129
|
+
errorPreview: storedPreview(span.errorPreview, span.error, error)
|
|
4016
4130
|
};
|
|
4017
4131
|
}
|
|
4018
4132
|
function rowToLogRecord(row) {
|
|
@@ -6927,11 +7041,15 @@ const FEEDBACK_FIELDS = {
|
|
|
6927
7041
|
const TRACE_SELECT = `
|
|
6928
7042
|
r.traceId AS traceId,
|
|
6929
7043
|
r.spanId AS rootSpanId,
|
|
7044
|
+
r.dedupeKey AS dedupeKey,
|
|
7045
|
+
r.organizationId AS organizationId,
|
|
6930
7046
|
r.name AS name,
|
|
6931
7047
|
r.entityId AS entityId,
|
|
6932
7048
|
r.parentSpanId AS parentSpanId,
|
|
6933
7049
|
r.metadataRaw AS metadata,
|
|
6934
|
-
r.
|
|
7050
|
+
r.inputPreview AS inputPreview,
|
|
7051
|
+
r.outputPreview AS outputPreview,
|
|
7052
|
+
r.errorPreview AS errorPreview,
|
|
6935
7053
|
r.threadId AS threadId,
|
|
6936
7054
|
r.resourceId AS resourceId,
|
|
6937
7055
|
r.startedAt AS startedAt,
|
|
@@ -6940,22 +7058,6 @@ const TRACE_SELECT = `
|
|
|
6940
7058
|
r.entityType AS entityType,
|
|
6941
7059
|
r.environment AS environment,
|
|
6942
7060
|
${TRACE_STATUS_SQL} AS status`;
|
|
6943
|
-
/** Candidate columns carried through the page-mode window sort (no payload blobs). */
|
|
6944
|
-
const TRACE_PAGE_COLUMNS = [
|
|
6945
|
-
"traceId",
|
|
6946
|
-
"rootSpanId",
|
|
6947
|
-
"name",
|
|
6948
|
-
"entityId",
|
|
6949
|
-
"parentSpanId",
|
|
6950
|
-
"threadId",
|
|
6951
|
-
"resourceId",
|
|
6952
|
-
"startedAt",
|
|
6953
|
-
"endedAt",
|
|
6954
|
-
"entityName",
|
|
6955
|
-
"entityType",
|
|
6956
|
-
"environment",
|
|
6957
|
-
"status"
|
|
6958
|
-
];
|
|
6959
7061
|
var ParameterBuilder = class {
|
|
6960
7062
|
params = {};
|
|
6961
7063
|
#next = 1;
|
|
@@ -7079,18 +7181,38 @@ function compileTenantScope(scope, parameters) {
|
|
|
7079
7181
|
if (scope.resourceId !== void 0) sql += `\n AND resourceId = ${parameters.add(scope.resourceId, "String")}`;
|
|
7080
7182
|
return sql;
|
|
7081
7183
|
}
|
|
7082
|
-
function compileClickHouseTraceScope(selection, relationCollections, parameters, scope, seedConjuncts = []) {
|
|
7184
|
+
function compileClickHouseTraceScope(selection, relationCollections, parameters, scope, seedConjuncts = [], options = {}) {
|
|
7083
7185
|
const from = parameters.add(selection.timeRange.from, "DateTime64(3, 'UTC')");
|
|
7084
7186
|
const to = parameters.add(selection.timeRange.to, "DateTime64(3, 'UTC')");
|
|
7085
7187
|
const tenant = compileTenantScope(scope, parameters);
|
|
7086
|
-
const
|
|
7188
|
+
const seedFilter = seedConjuncts.map((conjunct) => `\n AND (${compilePredicate(conjunct, parameters)})`).join("");
|
|
7189
|
+
const bound = options.delta ? `\n AND (traceId IN (SELECT traceId FROM delta_candidates))` : "";
|
|
7190
|
+
const windowTraces = (more) => `
|
|
7191
|
+
SELECT traceId
|
|
7192
|
+
FROM ${TABLE_TRACE_ROOTS} w
|
|
7193
|
+
WHERE startedAt >= ${from}
|
|
7194
|
+
AND startedAt < ${to}${tenant}${more}
|
|
7195
|
+
`;
|
|
7196
|
+
const delta = options.delta ? [`delta_candidates AS (
|
|
7197
|
+
SELECT traceId, max(cursorId) AS latestCursorId
|
|
7198
|
+
FROM ${TABLE_TRACE_ROOTS_DELTA}
|
|
7199
|
+
WHERE ${options.delta}
|
|
7200
|
+
AND traceId IN (${windowTraces("")})
|
|
7201
|
+
GROUP BY traceId
|
|
7202
|
+
)`] : [];
|
|
7203
|
+
const ctes = options.oneRootPerTrace ? [...delta, `root_scope AS (
|
|
7204
|
+
SELECT *
|
|
7205
|
+
FROM ${TABLE_TRACE_ROOTS} r
|
|
7206
|
+
WHERE startedAt >= ${from}
|
|
7207
|
+
AND startedAt < ${to}${tenant}${seedFilter}${bound}
|
|
7208
|
+
)`] : [`current_roots AS (
|
|
7087
7209
|
SELECT *
|
|
7088
7210
|
FROM ${TABLE_TRACE_ROOTS}
|
|
7089
7211
|
WHERE traceId IN (
|
|
7090
7212
|
SELECT traceId
|
|
7091
7213
|
FROM ${TABLE_TRACE_ROOTS} r
|
|
7092
7214
|
WHERE startedAt >= ${from}
|
|
7093
|
-
AND startedAt < ${to}${tenant}${
|
|
7215
|
+
AND startedAt < ${to}${tenant}${seedFilter}
|
|
7094
7216
|
)${tenant}
|
|
7095
7217
|
ORDER BY traceId, dedupeKey
|
|
7096
7218
|
LIMIT 1 BY traceId
|
|
@@ -7124,9 +7246,9 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
|
|
|
7124
7246
|
organizationId
|
|
7125
7247
|
FROM ${TABLE_SPAN_EVENTS}
|
|
7126
7248
|
WHERE isNotNull(traceId)
|
|
7127
|
-
AND traceId IN (SELECT traceId FROM root_scope)${tenant}
|
|
7249
|
+
AND traceId IN (${options.spanExistence && options.oneRootPerTrace ? windowTraces(bound) : "SELECT traceId FROM root_scope"})${tenant}${options.spanExistence ? "" : `
|
|
7128
7250
|
ORDER BY dedupeKey
|
|
7129
|
-
LIMIT 1 BY dedupeKey
|
|
7251
|
+
LIMIT 1 BY dedupeKey`}
|
|
7130
7252
|
)`);
|
|
7131
7253
|
if (relationCollections.has("scores")) ctes.push(`current_scores AS (
|
|
7132
7254
|
SELECT
|
|
@@ -7194,9 +7316,9 @@ function parseDeltaWatermark(value) {
|
|
|
7194
7316
|
* selection's time range and tenant scope that match its `where` predicate. Trace queries and
|
|
7195
7317
|
* trace aggregates both select from this CTE so they always see the same population.
|
|
7196
7318
|
*/
|
|
7197
|
-
function compileClickHouseTraceCandidates(selection, columns, parameters) {
|
|
7319
|
+
function compileClickHouseTraceCandidates(selection, columns, parameters, options = {}) {
|
|
7198
7320
|
const rootConjuncts = selection.where ? (selection.where.type === "boolean" && selection.where.operator === "and" ? selection.where.args : [selection.where]).filter((conjunct) => collectRelationCollections(conjunct).size === 0) : [];
|
|
7199
|
-
const ctes = compileClickHouseTraceScope(selection, collectRelationCollections(selection.where), parameters, selection.scope, rootConjuncts);
|
|
7321
|
+
const ctes = compileClickHouseTraceScope(selection, collectRelationCollections(selection.where), parameters, selection.scope, rootConjuncts, options);
|
|
7200
7322
|
const predicate = selection.where ? compilePredicate(selection.where, parameters) : "1";
|
|
7201
7323
|
ctes.push(`candidates AS (
|
|
7202
7324
|
SELECT ${columns}
|
|
@@ -7205,94 +7327,191 @@ function compileClickHouseTraceCandidates(selection, columns, parameters) {
|
|
|
7205
7327
|
)`);
|
|
7206
7328
|
return ctes;
|
|
7207
7329
|
}
|
|
7330
|
+
/**
|
|
7331
|
+
* `queryTraces()` statements read one root per trace and only test spans for existence
|
|
7332
|
+
* (TraceScopeOptions). The list statements carry narrow columns; `metadata` and the previews are fetched
|
|
7333
|
+
* for the returned rows afterwards (compileClickHouseTraceRootPayloads).
|
|
7334
|
+
*/
|
|
7335
|
+
const QUERY_TRACES_SCOPE = {
|
|
7336
|
+
oneRootPerTrace: true,
|
|
7337
|
+
spanExistence: true
|
|
7338
|
+
};
|
|
7339
|
+
const TRACE_LIST_SELECT = TRACE_SELECT.split("\n").filter((line) => !/ AS (metadata|inputPreview|outputPreview|errorPreview),$/.test(line)).join("\n");
|
|
7340
|
+
function traceOrder(plan) {
|
|
7341
|
+
return {
|
|
7342
|
+
field: resolveOrderField(plan.orderBy.field),
|
|
7343
|
+
direction: plan.orderBy.direction === "asc" ? "ASC" : "DESC"
|
|
7344
|
+
};
|
|
7345
|
+
}
|
|
7346
|
+
/**
|
|
7347
|
+
* Keyset cursor on a trace's sort value. Applied after each trace is reduced to one sort value:
|
|
7348
|
+
* filtering root rows first would let a trace with several roots (or unmerged copies with
|
|
7349
|
+
* different `endedAt`) come back on a later page under a root that sorts after the cursor.
|
|
7350
|
+
*/
|
|
7351
|
+
function keysetCursorCondition(plan, order, parameters) {
|
|
7352
|
+
if (plan.paginationMode !== "keyset" || !plan.cursor) return "";
|
|
7353
|
+
const comparison = order.direction === "ASC" ? ">" : "<";
|
|
7354
|
+
const sortValue = parameters.add(plan.cursor.sortValue, "DateTime64(3, 'UTC')");
|
|
7355
|
+
return `(sortValue ${comparison} ${sortValue} OR (sortValue = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")}))`;
|
|
7356
|
+
}
|
|
7357
|
+
/**
|
|
7358
|
+
* First statement of keyset and page mode: the page's traces (after the keyset cursor, `limit + 1`
|
|
7359
|
+
* for keyset mode, `perPage` at the page offset for page mode) as root keys, and the
|
|
7360
|
+
* number of matching traces on every row. Per trace, the root row that sorts first gives the sort
|
|
7361
|
+
* value and the key, so the key always names a row with that sort value. No blob columns are read.
|
|
7362
|
+
*/
|
|
7363
|
+
function compileClickHouseTraceQueryKeys(plan) {
|
|
7364
|
+
if (plan.paginationMode === "delta") throw new Error("Delta trace queries have no keys statement");
|
|
7365
|
+
const parameters = new ParameterBuilder();
|
|
7366
|
+
const order = traceOrder(plan);
|
|
7367
|
+
const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt", parameters, QUERY_TRACES_SCOPE);
|
|
7368
|
+
const cursor = keysetCursorCondition(plan, order, parameters);
|
|
7369
|
+
const pick = order.direction === "DESC" ? "max" : "min";
|
|
7370
|
+
const limit = plan.paginationMode === "page" ? `LIMIT ${parameters.add(plan.perPage, "UInt64")} OFFSET ${parameters.add(plan.page * plan.perPage, "UInt64")}` : `LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`;
|
|
7371
|
+
return {
|
|
7372
|
+
query: `WITH ${ctes.join(",\n")},
|
|
7373
|
+
per_trace AS (
|
|
7374
|
+
SELECT traceId, ${pick}(${order.field}) AS sortValue,
|
|
7375
|
+
${pick === "max" ? "argMax" : "argMin"}((dedupeKey, startedAt), ${order.field}) AS root
|
|
7376
|
+
FROM candidates
|
|
7377
|
+
GROUP BY traceId${cursor ? `\n HAVING ${cursor}` : ""}
|
|
7378
|
+
)
|
|
7379
|
+
SELECT traceId, root.1 AS dedupeKey, sortValue, root.2 AS rootStartedAt, count() OVER () AS __total
|
|
7380
|
+
FROM per_trace
|
|
7381
|
+
ORDER BY sortValue ${order.direction}, traceId ASC
|
|
7382
|
+
${limit}`,
|
|
7383
|
+
query_params: parameters.params,
|
|
7384
|
+
sharedSnapshot: true
|
|
7385
|
+
};
|
|
7386
|
+
}
|
|
7387
|
+
/**
|
|
7388
|
+
* Second statement of keyset and page mode: the rows of the keyed roots, with `metadata`
|
|
7389
|
+
* and the stored previews, read by sort-key tuple. Unmerged copies of a root key are redeliveries of one
|
|
7390
|
+
* span; the relation-free `where` conjuncts are re-applied so a copy that doesn't match isn't
|
|
7391
|
+
* returned, and the copy that sorts first is kept, as in the keys statement.
|
|
7392
|
+
*/
|
|
7393
|
+
function compileClickHouseTraceRowsByKey(plan, keys) {
|
|
7394
|
+
const parameters = new ParameterBuilder();
|
|
7395
|
+
const order = traceOrder(plan);
|
|
7396
|
+
const ts = (value) => parameters.add(value, "DateTime64(3, 'UTC')");
|
|
7397
|
+
const byEnd = order.field === "endedAt";
|
|
7398
|
+
const tuples = keys.map((key) => {
|
|
7399
|
+
const started = ts(byEnd ? key.rootStartedAt : key.sortValue);
|
|
7400
|
+
const id = `${parameters.add(key.traceId, "String")}, ${parameters.add(key.dedupeKey, "String")}`;
|
|
7401
|
+
return byEnd ? `(${started}, ${id}, ${ts(key.sortValue)})` : `(${started}, ${id})`;
|
|
7402
|
+
});
|
|
7403
|
+
const predicate = (plan.where ? (plan.where.type === "boolean" && plan.where.operator === "and" ? plan.where.args : [plan.where]).filter((conjunct) => collectRelationCollections(conjunct).size === 0) : []).map((conjunct) => `\n AND (${compilePredicate(conjunct, parameters)})`).join("");
|
|
7404
|
+
return {
|
|
7405
|
+
query: `SELECT ${TRACE_SELECT}
|
|
7406
|
+
FROM ${TABLE_TRACE_ROOTS} r
|
|
7407
|
+
WHERE (r.startedAt, r.traceId, r.dedupeKey${byEnd ? ", r.endedAt" : ""}) IN (${tuples.join(", ")})${compileTenantScope(plan.scope, parameters)}${predicate}
|
|
7408
|
+
ORDER BY ${order.field} ${order.direction}, traceId ASC
|
|
7409
|
+
LIMIT 1 BY traceId`,
|
|
7410
|
+
query_params: parameters.params
|
|
7411
|
+
};
|
|
7412
|
+
}
|
|
7413
|
+
/** Number of matching traces, for numbered pages past the last one (the keys statement returns no rows). */
|
|
7414
|
+
function compileClickHouseTraceQueryTotal(plan) {
|
|
7415
|
+
const parameters = new ParameterBuilder();
|
|
7416
|
+
return {
|
|
7417
|
+
query: `WITH ${compileClickHouseTraceCandidates(plan, "r.traceId AS traceId", parameters, QUERY_TRACES_SCOPE).join(",\n")}
|
|
7418
|
+
SELECT uniqExact(traceId) AS traces
|
|
7419
|
+
FROM candidates`,
|
|
7420
|
+
query_params: parameters.params,
|
|
7421
|
+
sharedSnapshot: true
|
|
7422
|
+
};
|
|
7423
|
+
}
|
|
7424
|
+
/**
|
|
7425
|
+
* The list statement of groups and delta mode (rows without `metadata` and the previews). Keyset
|
|
7426
|
+
* and page mode list with compileClickHouseTraceQueryKeys and compileClickHouseTraceRowsByKey.
|
|
7427
|
+
* A trace with several candidate rows (several roots, or unmerged copies of one) is listed in delta
|
|
7428
|
+
* mode under the row that sorts first in the list order, as in the keys statement; ties go to the
|
|
7429
|
+
* lowest dedupeKey, then the latest endedAt, so every poll picks the same row.
|
|
7430
|
+
*/
|
|
7208
7431
|
function compileClickHouseTraceQuery(plan, deltaHead) {
|
|
7209
7432
|
const parameters = new ParameterBuilder();
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7433
|
+
if (plan.result === "groups") {
|
|
7434
|
+
const ctes = compileClickHouseTraceCandidates(plan, TRACE_LIST_SELECT, parameters, QUERY_TRACES_SCOPE);
|
|
7435
|
+
const pageCondition = plan.cursor ? `AND threadId > ${parameters.add(plan.cursor.threadId, "String")}` : "";
|
|
7436
|
+
const limit = parameters.add(plan.limit + 1, "UInt64");
|
|
7437
|
+
return {
|
|
7438
|
+
query: `WITH ${ctes.join(",\n")}
|
|
7213
7439
|
SELECT threadId
|
|
7214
7440
|
FROM candidates
|
|
7215
|
-
WHERE isNotNull(threadId) ${
|
|
7441
|
+
WHERE isNotNull(threadId) ${pageCondition}
|
|
7216
7442
|
GROUP BY threadId
|
|
7217
7443
|
ORDER BY threadId ASC
|
|
7218
|
-
LIMIT ${
|
|
7219
|
-
query_params: parameters.params
|
|
7220
|
-
};
|
|
7221
|
-
if (plan.paginationMode === "delta") {
|
|
7222
|
-
const watermark = coreStorage.getTraceQueryDeltaWatermark(plan, "clickhouse");
|
|
7223
|
-
const after = watermark ? parseDeltaWatermark(watermark) : {
|
|
7224
|
-
cursorId: "0",
|
|
7225
|
-
traceId: ""
|
|
7226
|
-
};
|
|
7227
|
-
const lowerCursor = parameters.add(after.cursorId, "UInt64");
|
|
7228
|
-
return {
|
|
7229
|
-
query: `${candidates}, delta_candidates AS (
|
|
7230
|
-
SELECT traceId, max(cursorId) AS latestCursorId
|
|
7231
|
-
FROM ${TABLE_TRACE_ROOTS_DELTA}
|
|
7232
|
-
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")})` : ""}
|
|
7233
|
-
GROUP BY traceId
|
|
7234
|
-
)
|
|
7235
|
-
SELECT c.*, toString(d.latestCursorId) AS __delta_cursor
|
|
7236
|
-
FROM candidates c
|
|
7237
|
-
INNER JOIN delta_candidates d ON c.traceId = d.traceId
|
|
7238
|
-
ORDER BY d.latestCursorId ASC, c.traceId ASC
|
|
7239
|
-
LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
|
|
7240
|
-
query_params: parameters.params,
|
|
7241
|
-
sharedSnapshot: true
|
|
7242
|
-
};
|
|
7243
|
-
}
|
|
7244
|
-
const orderField = resolveOrderField(plan.orderBy.field);
|
|
7245
|
-
const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
|
|
7246
|
-
if (plan.paginationMode === "page") {
|
|
7247
|
-
const onPage = `__row_position > ${parameters.add(plan.page * plan.perPage, "UInt64")} AND __row_position <= ${parameters.add((plan.page + 1) * plan.perPage, "UInt64")}`;
|
|
7248
|
-
return {
|
|
7249
|
-
query: `${candidates},
|
|
7250
|
-
page_rows AS (
|
|
7251
|
-
SELECT
|
|
7252
|
-
${TRACE_PAGE_COLUMNS.join(",\n ")},
|
|
7253
|
-
row_number() OVER (ORDER BY ${orderField} ${direction}, traceId ASC) AS __row_position,
|
|
7254
|
-
count() OVER () AS total
|
|
7255
|
-
FROM candidates
|
|
7256
|
-
)
|
|
7257
|
-
SELECT *, if(${onPage}, 0, 1) AS __metadata
|
|
7258
|
-
FROM page_rows
|
|
7259
|
-
WHERE (${onPage}) OR __row_position = 1
|
|
7260
|
-
ORDER BY __metadata ASC, __row_position ASC`,
|
|
7444
|
+
LIMIT ${limit}`,
|
|
7261
7445
|
query_params: parameters.params,
|
|
7262
7446
|
sharedSnapshot: true
|
|
7263
7447
|
};
|
|
7264
7448
|
}
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7449
|
+
if (plan.paginationMode !== "delta") throw new Error("Keyset and page trace queries use compileClickHouseTraceQueryKeys and compileClickHouseTraceRowsByKey");
|
|
7450
|
+
const order = traceOrder(plan);
|
|
7451
|
+
const watermark = coreStorage.getTraceQueryDeltaWatermark(plan, "clickhouse");
|
|
7452
|
+
const after = watermark ? parseDeltaWatermark(watermark) : {
|
|
7453
|
+
cursorId: "0",
|
|
7454
|
+
traceId: ""
|
|
7455
|
+
};
|
|
7456
|
+
const lowerCursor = parameters.add(after.cursorId, "UInt64");
|
|
7457
|
+
const lower = `tuple(${lowerCursor}, ${parameters.add(after.traceId, "String")})`;
|
|
7458
|
+
const upper = deltaHead ? `AND tuple(cursorId, traceId) <= tuple(${parameters.add(deltaHead.cursorId, "UInt64")}, ${parameters.add(deltaHead.traceId, "String")})` : "";
|
|
7459
|
+
const ctes = compileClickHouseTraceCandidates(plan, TRACE_LIST_SELECT, parameters, {
|
|
7460
|
+
...QUERY_TRACES_SCOPE,
|
|
7461
|
+
delta: `cursorId >= ${lowerCursor} AND tuple(cursorId, traceId) > ${lower} ${upper}`
|
|
7462
|
+
});
|
|
7271
7463
|
const limit = parameters.add(plan.limit + 1, "UInt64");
|
|
7272
7464
|
return {
|
|
7273
|
-
query:
|
|
7274
|
-
SELECT
|
|
7275
|
-
FROM candidates
|
|
7276
|
-
|
|
7277
|
-
ORDER BY
|
|
7465
|
+
query: `WITH ${ctes.join(",\n")}
|
|
7466
|
+
SELECT c.*, toString(d.latestCursorId) AS __delta_cursor
|
|
7467
|
+
FROM candidates c
|
|
7468
|
+
INNER JOIN delta_candidates d ON c.traceId = d.traceId
|
|
7469
|
+
ORDER BY d.latestCursorId ASC, c.traceId ASC, c.${order.field} ${order.direction}, c.dedupeKey ASC, c.endedAt DESC
|
|
7470
|
+
LIMIT 1 BY c.traceId
|
|
7278
7471
|
LIMIT ${limit}`,
|
|
7279
|
-
query_params: parameters.params
|
|
7472
|
+
query_params: parameters.params,
|
|
7473
|
+
sharedSnapshot: true
|
|
7474
|
+
};
|
|
7475
|
+
}
|
|
7476
|
+
/** Map key matching a payload row to the listed row it was read for. */
|
|
7477
|
+
function traceRootRowKey(row) {
|
|
7478
|
+
return JSON.stringify([
|
|
7479
|
+
String(row.traceId),
|
|
7480
|
+
String(row.dedupeKey),
|
|
7481
|
+
row.organizationId == null ? null : String(row.organizationId),
|
|
7482
|
+
row.resourceId == null ? null : String(row.resourceId)
|
|
7483
|
+
]);
|
|
7484
|
+
}
|
|
7485
|
+
/** A listed row's {@link ClickHouseTraceRootRowKey}. */
|
|
7486
|
+
function toTraceRootRowKey(row) {
|
|
7487
|
+
return {
|
|
7488
|
+
traceId: String(row.traceId),
|
|
7489
|
+
dedupeKey: String(row.dedupeKey),
|
|
7490
|
+
startedAt: asIsoTimestamp(row.startedAt),
|
|
7491
|
+
endedAt: asIsoTimestamp(row.endedAt),
|
|
7492
|
+
organizationId: row.organizationId == null ? null : String(row.organizationId),
|
|
7493
|
+
resourceId: row.resourceId == null ? null : String(row.resourceId)
|
|
7280
7494
|
};
|
|
7281
7495
|
}
|
|
7282
7496
|
/**
|
|
7283
|
-
* Fetches
|
|
7284
|
-
*
|
|
7285
|
-
*
|
|
7286
|
-
*
|
|
7287
|
-
*
|
|
7497
|
+
* Fetches payloads for listed rows: `metadata` and the stored previews, or `input` / `output` /
|
|
7498
|
+
* `error` alone for rows written before the previews were stored. Each row is looked up by its
|
|
7499
|
+
* own identity: the trace_roots sort key `(startedAt, traceId, dedupeKey)`, so only the page's
|
|
7500
|
+
* granules are read; `endedAt`, so the payload comes from the same version as the listed row (a
|
|
7501
|
+
* root can have unmerged versions in different `endedAt` partitions); and its own
|
|
7502
|
+
* `organizationId` / `resourceId`, so a page holding several tenants' rows that share a trace and
|
|
7503
|
+
* span id never takes another tenant's payload. NULL tenant columns match only NULL. Callers
|
|
7504
|
+
* match the returned rows back with {@link traceRootRowKey}.
|
|
7288
7505
|
*/
|
|
7289
|
-
function compileClickHouseTraceRootPayloads(keys) {
|
|
7506
|
+
function compileClickHouseTraceRootPayloads(keys, scope, columns = "payload") {
|
|
7290
7507
|
const parameters = new ParameterBuilder();
|
|
7508
|
+
const tenant = (value) => `${parameters.add(value ?? "", "String")}, ${value === null ? 1 : 0}`;
|
|
7509
|
+
const tuples = keys.map((key) => `(${parameters.add(key.startedAt, "DateTime64(3, 'UTC')")}, ${parameters.add(key.traceId, "String")}, ${parameters.add(key.dedupeKey, "String")}, ${parameters.add(key.endedAt, "DateTime64(3, 'UTC')")}, ${tenant(key.organizationId)}, ${tenant(key.resourceId)})`);
|
|
7291
7510
|
return {
|
|
7292
|
-
query: `SELECT traceId, spanId AS rootSpanId, metadataRaw AS metadata, input
|
|
7511
|
+
query: `SELECT traceId, spanId AS rootSpanId, dedupeKey, organizationId, resourceId, ${columns === "payload" ? `metadataRaw AS metadata, ${PREVIEW_COLUMNS.join(", ")}` : "input, output, error"}
|
|
7293
7512
|
FROM ${TABLE_TRACE_ROOTS}
|
|
7294
|
-
WHERE (startedAt, traceId,
|
|
7295
|
-
LIMIT 1 BY traceId,
|
|
7513
|
+
WHERE (startedAt, traceId, dedupeKey, endedAt, ifNull(organizationId, ''), isNull(organizationId), ifNull(resourceId, ''), isNull(resourceId)) IN (${tuples.join(", ")})${compileTenantScope(scope, parameters)}
|
|
7514
|
+
LIMIT 1 BY traceId, dedupeKey, organizationId, resourceId`,
|
|
7296
7515
|
query_params: parameters.params
|
|
7297
7516
|
};
|
|
7298
7517
|
}
|
|
@@ -7486,56 +7705,15 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7486
7705
|
deltaCursor: coreStorage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(deltaHead))
|
|
7487
7706
|
});
|
|
7488
7707
|
}
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
const
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
const payloadRows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: remaining() }, compileClickHouseTraceRootPayloads(pageRows.map((row) => ({
|
|
7496
|
-
traceId: String(row.traceId),
|
|
7497
|
-
rootSpanId: String(row.rootSpanId),
|
|
7498
|
-
startedAt: asIsoTimestamp(row.startedAt),
|
|
7499
|
-
endedAt: asIsoTimestamp(row.endedAt)
|
|
7500
|
-
}))));
|
|
7501
|
-
for (const payload of payloadRows) payloads.set(`${payload.traceId}\u0000${payload.rootSpanId}`, payload);
|
|
7502
|
-
}
|
|
7503
|
-
const traces = pageRows.map((row) => ({
|
|
7504
|
-
...row,
|
|
7505
|
-
...payloads.get(`${row.traceId}\u0000${row.rootSpanId}`)
|
|
7506
|
-
})).map((row) => ({
|
|
7507
|
-
traceId: String(row.traceId),
|
|
7508
|
-
rootSpanId: String(row.rootSpanId),
|
|
7509
|
-
name: row.name,
|
|
7510
|
-
entityId: row.entityId ?? null,
|
|
7511
|
-
parentSpanId: row.parentSpanId ?? null,
|
|
7512
|
-
createdAt: asIsoTimestamp(row.startedAt),
|
|
7513
|
-
metadata: parseJson(row.metadata) ?? null,
|
|
7514
|
-
inputPreview: coreStorage.buildInputPreview(row.input) ?? null,
|
|
7515
|
-
threadId: row.threadId == null ? null : String(row.threadId),
|
|
7516
|
-
resourceId: row.resourceId == null ? null : String(row.resourceId),
|
|
7517
|
-
startedAt: asIsoTimestamp(row.startedAt),
|
|
7518
|
-
endedAt: asIsoTimestamp(row.endedAt),
|
|
7519
|
-
entityName: row.entityName == null ? null : String(row.entityName),
|
|
7520
|
-
entityType: row.entityType == null ? null : String(row.entityType),
|
|
7521
|
-
environment: row.environment == null ? null : String(row.environment),
|
|
7522
|
-
status: row.status
|
|
7523
|
-
}));
|
|
7524
|
-
return coreStorage.traceQueryResponseSchema.parse({
|
|
7525
|
-
traces,
|
|
7526
|
-
...deltaHead ? { deltaCursor: coreStorage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(deltaHead)) } : {},
|
|
7527
|
-
pagination: {
|
|
7528
|
-
total,
|
|
7529
|
-
page: plan.page,
|
|
7530
|
-
perPage: plan.perPage,
|
|
7531
|
-
hasMore: (plan.page + 1) * plan.perPage < total
|
|
7532
|
-
}
|
|
7533
|
-
});
|
|
7534
|
-
}
|
|
7535
|
-
const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: deltaHead ? remaining() : timeoutMs }, compileClickHouseTraceQuery(plan, deltaHead));
|
|
7536
|
-
const visibleRows = rows.slice(0, plan.limit);
|
|
7708
|
+
let first = !deltaHead;
|
|
7709
|
+
const run = (compiled) => {
|
|
7710
|
+
const budget = first ? timeoutMs : remaining();
|
|
7711
|
+
first = false;
|
|
7712
|
+
return runWithClickHouseTraceQueryTimeout(client, { timeoutMs: budget }, compiled);
|
|
7713
|
+
};
|
|
7537
7714
|
if (plan.result === "groups") {
|
|
7538
|
-
const
|
|
7715
|
+
const rows = await run(compileClickHouseTraceQuery(plan));
|
|
7716
|
+
const groups = rows.slice(0, plan.limit).map((row) => ({ threadId: String(row.threadId) }));
|
|
7539
7717
|
const last = groups.at(-1);
|
|
7540
7718
|
return coreStorage.traceQueryResponseSchema.parse({
|
|
7541
7719
|
groups,
|
|
@@ -7545,7 +7723,32 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7545
7723
|
}) : null }
|
|
7546
7724
|
});
|
|
7547
7725
|
}
|
|
7548
|
-
|
|
7726
|
+
/** Rows written before the previews were stored have none; theirs are built from the payloads. */
|
|
7727
|
+
const toTraceRecords = async (rows) => {
|
|
7728
|
+
const missing = rows.filter(needsPreviewPayloads);
|
|
7729
|
+
const payloads = /* @__PURE__ */ new Map();
|
|
7730
|
+
if (missing.length > 0) {
|
|
7731
|
+
const payloadRows = await run(compileClickHouseTraceRootPayloads(missing.map(toTraceRootRowKey), plan.scope, "previewPayloads"));
|
|
7732
|
+
for (const row of payloadRows) payloads.set(traceRootRowKey(row), row);
|
|
7733
|
+
}
|
|
7734
|
+
return rows.map((row) => toTraceRecord({
|
|
7735
|
+
...row,
|
|
7736
|
+
...resolveStoredPreviews(row, payloads.get(traceRootRowKey(row)))
|
|
7737
|
+
}));
|
|
7738
|
+
};
|
|
7739
|
+
/** Lists rows, then fetches `metadata` and the previews for the rows that are returned. */
|
|
7740
|
+
const listTraces = async (rows) => {
|
|
7741
|
+
const payloads = /* @__PURE__ */ new Map();
|
|
7742
|
+
if (rows.length > 0) {
|
|
7743
|
+
const payloadRows = await run(compileClickHouseTraceRootPayloads(rows.map(toTraceRootRowKey), plan.scope));
|
|
7744
|
+
for (const payload of payloadRows) payloads.set(traceRootRowKey(payload), payload);
|
|
7745
|
+
}
|
|
7746
|
+
return toTraceRecords(rows.map((row) => ({
|
|
7747
|
+
...row,
|
|
7748
|
+
...payloads.get(traceRootRowKey(row))
|
|
7749
|
+
})));
|
|
7750
|
+
};
|
|
7751
|
+
const toTraceRecord = (row) => ({
|
|
7549
7752
|
traceId: String(row.traceId),
|
|
7550
7753
|
rootSpanId: String(row.rootSpanId),
|
|
7551
7754
|
name: row.name,
|
|
@@ -7553,7 +7756,9 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7553
7756
|
parentSpanId: row.parentSpanId ?? null,
|
|
7554
7757
|
createdAt: asIsoTimestamp(row.startedAt),
|
|
7555
7758
|
metadata: parseJson(row.metadata) ?? null,
|
|
7556
|
-
inputPreview:
|
|
7759
|
+
inputPreview: row.inputPreview ?? null,
|
|
7760
|
+
outputPreview: row.outputPreview ?? null,
|
|
7761
|
+
errorPreview: row.errorPreview ?? null,
|
|
7557
7762
|
threadId: row.threadId == null ? null : String(row.threadId),
|
|
7558
7763
|
resourceId: row.resourceId == null ? null : String(row.resourceId),
|
|
7559
7764
|
startedAt: asIsoTimestamp(row.startedAt),
|
|
@@ -7562,9 +7767,11 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7562
7767
|
entityType: row.entityType == null ? null : String(row.entityType),
|
|
7563
7768
|
environment: row.environment == null ? null : String(row.environment),
|
|
7564
7769
|
status: row.status
|
|
7565
|
-
})
|
|
7566
|
-
const last = traces.at(-1);
|
|
7770
|
+
});
|
|
7567
7771
|
if (plan.paginationMode === "delta") {
|
|
7772
|
+
const rows = await run(compileClickHouseTraceQuery(plan, deltaHead));
|
|
7773
|
+
const visibleRows = rows.slice(0, plan.limit);
|
|
7774
|
+
const traces = await listTraces(visibleRows);
|
|
7568
7775
|
const lastRow = visibleRows.at(-1);
|
|
7569
7776
|
let watermark = lastRow ? {
|
|
7570
7777
|
cursorId: String(lastRow.__delta_cursor),
|
|
@@ -7581,12 +7788,36 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7581
7788
|
deltaCursor: coreStorage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(watermark))
|
|
7582
7789
|
});
|
|
7583
7790
|
}
|
|
7791
|
+
const keys = await run(compileClickHouseTraceQueryKeys(plan));
|
|
7792
|
+
let total = Number(keys[0]?.__total ?? 0);
|
|
7793
|
+
if (plan.paginationMode === "page" && keys.length === 0 && plan.page > 0) {
|
|
7794
|
+
const [stats] = await run(compileClickHouseTraceQueryTotal(plan));
|
|
7795
|
+
total = Number(stats?.traces ?? 0);
|
|
7796
|
+
}
|
|
7797
|
+
const visible = plan.paginationMode === "page" ? keys : keys.slice(0, plan.limit);
|
|
7798
|
+
const rows = visible.length > 0 ? await run(compileClickHouseTraceRowsByKey(plan, visible.map((key) => ({
|
|
7799
|
+
traceId: String(key.traceId),
|
|
7800
|
+
dedupeKey: String(key.dedupeKey),
|
|
7801
|
+
sortValue: asIsoTimestamp(key.sortValue),
|
|
7802
|
+
rootStartedAt: asIsoTimestamp(key.rootStartedAt)
|
|
7803
|
+
})))) : [];
|
|
7804
|
+
if (plan.paginationMode === "page") return coreStorage.traceQueryResponseSchema.parse({
|
|
7805
|
+
traces: await toTraceRecords(rows),
|
|
7806
|
+
...deltaHead ? { deltaCursor: coreStorage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(deltaHead)) } : {},
|
|
7807
|
+
pagination: {
|
|
7808
|
+
total,
|
|
7809
|
+
page: plan.page,
|
|
7810
|
+
perPage: plan.perPage,
|
|
7811
|
+
hasMore: (plan.page + 1) * plan.perPage < total
|
|
7812
|
+
}
|
|
7813
|
+
});
|
|
7814
|
+
const lastKey = visible.at(-1);
|
|
7584
7815
|
return coreStorage.traceQueryResponseSchema.parse({
|
|
7585
|
-
traces,
|
|
7586
|
-
page: { next:
|
|
7816
|
+
traces: await toTraceRecords(rows),
|
|
7817
|
+
page: { next: keys.length > plan.limit && lastKey ? coreStorage.encodeTraceQueryCursor(plan, {
|
|
7587
7818
|
result: "traces",
|
|
7588
|
-
sortValue:
|
|
7589
|
-
traceId:
|
|
7819
|
+
sortValue: asIsoTimestamp(lastKey.sortValue),
|
|
7820
|
+
traceId: String(lastKey.traceId)
|
|
7590
7821
|
}) : null }
|
|
7591
7822
|
});
|
|
7592
7823
|
}
|
|
@@ -7756,13 +7987,29 @@ async function querySpans(client, plan, timeoutMs) {
|
|
|
7756
7987
|
page: { next: null }
|
|
7757
7988
|
};
|
|
7758
7989
|
const payloadWhere = `tuple(${identitySetColumns}, endedAt) IN {page:Array(Tuple(UInt8, String, UInt8, String, String, String, DateTime64(3, 'UTC')))}`;
|
|
7759
|
-
const
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
FROM (SELECT ${identities}, any(tuple(${displayFields.map(fieldExpression).join(", ")}
|
|
7990
|
+
const pageParameter = (rows) => rows.map((row) => new TupleParam([...identityValues(row), row.endedAt.replace("T", " ").replace(/Z$/, "")]));
|
|
7991
|
+
const rowsWithPreviews = await run({
|
|
7992
|
+
query: `SELECT ${identities}, ${[...displayFields, ...PREVIEW_COLUMNS].map((field, index) => `data.${index + 1} AS ${field}`).join(", ")}
|
|
7993
|
+
FROM (SELECT ${identities}, any(tuple(${[...displayFields.map(fieldExpression), ...PREVIEW_COLUMNS].join(", ")})) AS data
|
|
7763
7994
|
FROM ${TABLE_SPAN_EVENTS} e WHERE ${payloadWhere} GROUP BY ${identities})`,
|
|
7764
|
-
query_params: { page: page
|
|
7995
|
+
query_params: { page: pageParameter(page) }
|
|
7765
7996
|
});
|
|
7997
|
+
const missingKeys = new Set(rowsWithPreviews.filter(needsPreviewPayloads).map(coreStorage.spanQueryIdentityKey));
|
|
7998
|
+
const missing = page.filter((row) => missingKeys.has(coreStorage.spanQueryIdentityKey(row)));
|
|
7999
|
+
const previewPayloads = /* @__PURE__ */ new Map();
|
|
8000
|
+
if (missing.length) {
|
|
8001
|
+
const rows = await run({
|
|
8002
|
+
query: `SELECT ${identities}, data.1 AS input, data.2 AS output, data.3 AS error
|
|
8003
|
+
FROM (SELECT ${identities}, any(tuple(input, output, error)) AS data
|
|
8004
|
+
FROM ${TABLE_SPAN_EVENTS} e WHERE ${payloadWhere} GROUP BY ${identities})`,
|
|
8005
|
+
query_params: { page: pageParameter(missing) }
|
|
8006
|
+
});
|
|
8007
|
+
for (const row of rows) previewPayloads.set(coreStorage.spanQueryIdentityKey(row), row);
|
|
8008
|
+
}
|
|
8009
|
+
const payloads = rowsWithPreviews.map((row) => ({
|
|
8010
|
+
...row,
|
|
8011
|
+
...resolveStoredPreviews(row, previewPayloads.get(coreStorage.spanQueryIdentityKey(row)))
|
|
8012
|
+
}));
|
|
7766
8013
|
const metricWhere = `${identitySetKey} IN {page:Array(Tuple(UInt8, String, UInt8, String, String, String))}`;
|
|
7767
8014
|
const metrics = await run({
|
|
7768
8015
|
query: `SELECT ${identities}, data.1 AS name, data.2 AS estimatedCost,
|
|
@@ -7776,17 +8023,16 @@ async function querySpans(client, plan, timeoutMs) {
|
|
|
7776
8023
|
});
|
|
7777
8024
|
if (metrics.length > coreStorage.SPAN_QUERY_MAX_COST_METRICS) throw new coreStorage.TraceQueryResourceLimitError();
|
|
7778
8025
|
const byIdentity = new Map(payloads.map((row) => [coreStorage.spanQueryIdentityKey(row), row]));
|
|
7779
|
-
const
|
|
8026
|
+
const result = buildSpanQueryResponse(plan, page.flatMap((identity) => {
|
|
7780
8027
|
const row = byIdentity.get(coreStorage.spanQueryIdentityKey(identity));
|
|
7781
8028
|
if (!row) return [];
|
|
7782
|
-
const { inputPreview: _input, outputPreview: _output, ...metadata } = row;
|
|
8029
|
+
const { inputPreview: _input, outputPreview: _output, errorPreview: _error, ...metadata } = row;
|
|
7783
8030
|
return [{
|
|
7784
8031
|
...metadata,
|
|
7785
8032
|
...identity,
|
|
7786
8033
|
durationMs: Number(row.durationMs)
|
|
7787
8034
|
}];
|
|
7788
|
-
});
|
|
7789
|
-
const result = coreStorage.buildSpanQueryResponse(plan, hydrated, payloads, metrics);
|
|
8035
|
+
}), payloads, metrics);
|
|
7790
8036
|
const last = page.at(-1);
|
|
7791
8037
|
result.page.next = selected.length > plan.limit ? coreStorage.encodeSpanQueryCursor(plan, {
|
|
7792
8038
|
organizationId: last.organizationId,
|
|
@@ -8090,9 +8336,9 @@ async function getRootSpan(client, args) {
|
|
|
8090
8336
|
return { span: rowToSpanRecord(rows[0]) };
|
|
8091
8337
|
}
|
|
8092
8338
|
/**
|
|
8093
|
-
* Columns a trace list renders.
|
|
8094
|
-
*
|
|
8095
|
-
*
|
|
8339
|
+
* Columns a trace list renders. The previews are the stored ones; rows written
|
|
8340
|
+
* before they were stored get theirs from the payload columns, fetched for just
|
|
8341
|
+
* those rows (see `withPreviewPayloads`).
|
|
8096
8342
|
*/
|
|
8097
8343
|
const LIGHT_TRACE_ROOT_FIELDS = [
|
|
8098
8344
|
"traceId",
|
|
@@ -8107,10 +8353,13 @@ const LIGHT_TRACE_ROOT_FIELDS = [
|
|
|
8107
8353
|
"entityId",
|
|
8108
8354
|
"entityName",
|
|
8109
8355
|
"threadId",
|
|
8356
|
+
"organizationId",
|
|
8110
8357
|
"resourceId",
|
|
8111
8358
|
"error",
|
|
8112
8359
|
"metadataRaw",
|
|
8113
|
-
"
|
|
8360
|
+
"inputPreview",
|
|
8361
|
+
"outputPreview",
|
|
8362
|
+
"errorPreview"
|
|
8114
8363
|
];
|
|
8115
8364
|
const FULL_PROJECTION = {
|
|
8116
8365
|
innerSelect: "*",
|
|
@@ -8119,7 +8368,7 @@ const FULL_PROJECTION = {
|
|
|
8119
8368
|
};
|
|
8120
8369
|
const LIGHT_PROJECTION = {
|
|
8121
8370
|
innerSelect: ["dedupeKey", ...LIGHT_TRACE_ROOT_FIELDS].join(", "),
|
|
8122
|
-
outerSelect: LIGHT_TRACE_ROOT_FIELDS.join(", "),
|
|
8371
|
+
outerSelect: ["dedupeKey", ...LIGHT_TRACE_ROOT_FIELDS].join(", "),
|
|
8123
8372
|
deltaSelect: LIGHT_TRACE_ROOT_FIELDS.filter((field) => field !== "startedAt" && field !== "traceId").map((field) => `r.${field}`).join(", ")
|
|
8124
8373
|
};
|
|
8125
8374
|
/**
|
|
@@ -8165,7 +8414,7 @@ async function listTraceRows(client, args, strategy, projection, mapRows) {
|
|
|
8165
8414
|
const rows = await queryTracesAfterCursor(client, projection.deltaSelect, whereClause, params, limit, afterCursor);
|
|
8166
8415
|
const visibleRows = rows.slice(0, limit);
|
|
8167
8416
|
return {
|
|
8168
|
-
spans: mapRows(visibleRows),
|
|
8417
|
+
spans: await mapRows(visibleRows),
|
|
8169
8418
|
delta: {
|
|
8170
8419
|
limit,
|
|
8171
8420
|
hasMore: rows.length > limit
|
|
@@ -8240,7 +8489,7 @@ async function listTraceRows(client, args, strategy, projection, mapRows) {
|
|
|
8240
8489
|
perPage,
|
|
8241
8490
|
hasMore: (page + 1) * perPage < total
|
|
8242
8491
|
},
|
|
8243
|
-
spans: mapRows(rows),
|
|
8492
|
+
spans: await mapRows(rows),
|
|
8244
8493
|
...deltaCursorEnabled ? { deltaCursor: currentDeltaCursor } : {}
|
|
8245
8494
|
};
|
|
8246
8495
|
}
|
|
@@ -8249,12 +8498,32 @@ async function listTraces(client, args, strategy) {
|
|
|
8249
8498
|
return listTraceRows(client, args, strategy, FULL_PROJECTION, (rows) => toTraceSpans(rows.map(rowToSpanRecord)));
|
|
8250
8499
|
}
|
|
8251
8500
|
/**
|
|
8501
|
+
* Adds `input` / `output` / `error` to the rows written before previews were stored, read from
|
|
8502
|
+
* the same root row version (same `endedAt`) the list kept. A page can hold several tenants' rows,
|
|
8503
|
+
* so each row is looked up by its own identity, tenant included, not by one shared scope.
|
|
8504
|
+
*/
|
|
8505
|
+
async function withPreviewPayloads(client, rows) {
|
|
8506
|
+
const missing = rows.filter(needsPreviewPayloads);
|
|
8507
|
+
if (missing.length === 0) return rows;
|
|
8508
|
+
const compiled = compileClickHouseTraceRootPayloads(missing.map(toTraceRootRowKey), void 0, "previewPayloads");
|
|
8509
|
+
const result = await client.query({
|
|
8510
|
+
...compiled,
|
|
8511
|
+
format: "JSONEachRow",
|
|
8512
|
+
clickhouse_settings: CH_SETTINGS
|
|
8513
|
+
});
|
|
8514
|
+
const payloads = new Map((await result.json()).map((row) => [traceRootRowKey(row), row]));
|
|
8515
|
+
return rows.map((row) => needsPreviewPayloads(row) ? {
|
|
8516
|
+
...row,
|
|
8517
|
+
...payloads.get(traceRootRowKey(row))
|
|
8518
|
+
} : row);
|
|
8519
|
+
}
|
|
8520
|
+
/**
|
|
8252
8521
|
* List traces projecting only the columns a trace list renders.
|
|
8253
|
-
* Skips the attributes/output blobs and
|
|
8254
|
-
*
|
|
8522
|
+
* Skips the attributes/input/output blobs and reads the stored previews instead,
|
|
8523
|
+
* so the response payload stays flat as traces grow.
|
|
8255
8524
|
*/
|
|
8256
8525
|
async function listTracesLight(client, args, strategy) {
|
|
8257
|
-
return listTraceRows(client, args, strategy, LIGHT_PROJECTION, (rows) => rows.map(rowToLightSpanRecord));
|
|
8526
|
+
return listTraceRows(client, args, strategy, LIGHT_PROJECTION, async (rows) => (await withPreviewPayloads(client, rows)).map(rowToLightSpanRecord));
|
|
8258
8527
|
}
|
|
8259
8528
|
async function queryTracesAfterCursor(client, deltaSelect, whereClause, params, limit, cursorId) {
|
|
8260
8529
|
const deltaKeys = `SELECT startedAt, traceId, dedupeKey FROM ${TABLE_TRACE_ROOTS_DELTA} WHERE cursorId > {afterCursor:UInt64}`;
|