@mastra/pg 1.31.2-alpha.0 → 1.31.2-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/index.cjs +406 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +407 -147
- package/dist/index.js.map +1 -1
- package/dist/storage/db/sanitize-json.d.ts +2 -0
- package/dist/storage/db/sanitize-json.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/ddl.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/helpers.d.ts +7 -0
- package/dist/storage/domains/observability/v-next/helpers.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +2 -1
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/preview-compat.d.ts +47 -0
- package/dist/storage/domains/observability/v-next/preview-compat.d.ts.map +1 -0
- package/dist/storage/domains/observability/v-next/signal-schema.d.ts +12 -0
- package/dist/storage/domains/observability/v-next/signal-schema.d.ts.map +1 -1
- 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/sql.d.ts +11 -0
- package/dist/storage/domains/observability/v-next/sql.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/traces.d.ts +3 -1
- package/dist/storage/domains/observability/v-next/traces.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
2
2
|
import * as coreStorage from "@mastra/core/storage";
|
|
3
|
-
import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, 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_MEMORY_RUN_FENCES, 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_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
3
|
+
import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, 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_MEMORY_RUN_FENCES, 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_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, computeTraceStatus, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
4
4
|
import { parseFieldKey, parseSqlIdentifier } from "@mastra/core/utils";
|
|
5
5
|
import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/vector";
|
|
6
6
|
import { Mutex } from "async-mutex";
|
|
@@ -2448,6 +2448,10 @@ function repairJson(value) {
|
|
|
2448
2448
|
}
|
|
2449
2449
|
return value;
|
|
2450
2450
|
}
|
|
2451
|
+
/** Repair text for a PostgreSQL `text` column, which rejects NUL characters. */
|
|
2452
|
+
function toPgText(value) {
|
|
2453
|
+
return repair(value);
|
|
2454
|
+
}
|
|
2451
2455
|
/** Serialize JSON values without emitting Unicode sequences PostgreSQL rejects. */
|
|
2452
2456
|
function toPgJson(value) {
|
|
2453
2457
|
const json = JSON.stringify(value);
|
|
@@ -16051,6 +16055,21 @@ const SPAN_EVENT_COLUMNS = [
|
|
|
16051
16055
|
name: "requestContext",
|
|
16052
16056
|
type: "jsonb",
|
|
16053
16057
|
nullable: true
|
|
16058
|
+
},
|
|
16059
|
+
{
|
|
16060
|
+
name: "inputPreview",
|
|
16061
|
+
type: "text",
|
|
16062
|
+
nullable: true
|
|
16063
|
+
},
|
|
16064
|
+
{
|
|
16065
|
+
name: "outputPreview",
|
|
16066
|
+
type: "text",
|
|
16067
|
+
nullable: true
|
|
16068
|
+
},
|
|
16069
|
+
{
|
|
16070
|
+
name: "errorPreview",
|
|
16071
|
+
type: "text",
|
|
16072
|
+
nullable: true
|
|
16054
16073
|
}
|
|
16055
16074
|
];
|
|
16056
16075
|
const METRIC_EVENT_COLUMNS = [
|
|
@@ -16706,15 +16725,27 @@ function allTableDDL(schema, mode) {
|
|
|
16706
16725
|
* default does not rewrite the table, and the ALTER cascades to partitions.
|
|
16707
16726
|
*/
|
|
16708
16727
|
function additiveColumns(schema) {
|
|
16709
|
-
return [
|
|
16710
|
-
|
|
16711
|
-
|
|
16712
|
-
|
|
16713
|
-
|
|
16714
|
-
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16728
|
+
return [
|
|
16729
|
+
{
|
|
16730
|
+
table: TABLE_SPAN_EVENTS,
|
|
16731
|
+
column: "isPending",
|
|
16732
|
+
ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "isPending" boolean NOT NULL DEFAULT false`
|
|
16733
|
+
},
|
|
16734
|
+
...[
|
|
16735
|
+
"inputPreview",
|
|
16736
|
+
"outputPreview",
|
|
16737
|
+
"errorPreview"
|
|
16738
|
+
].map((column) => ({
|
|
16739
|
+
table: TABLE_SPAN_EVENTS,
|
|
16740
|
+
column,
|
|
16741
|
+
ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "${column}" text`
|
|
16742
|
+
})),
|
|
16743
|
+
{
|
|
16744
|
+
table: TABLE_FEEDBACK_EVENTS,
|
|
16745
|
+
column: "reviewStatus",
|
|
16746
|
+
ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)} ADD COLUMN IF NOT EXISTS "reviewStatus" text NOT NULL DEFAULT 'needs-review'`
|
|
16747
|
+
}
|
|
16748
|
+
];
|
|
16718
16749
|
}
|
|
16719
16750
|
/** Existence probe for an additive column. Params: schema, table, column. */
|
|
16720
16751
|
const columnExistsSQL = `SELECT 1 FROM information_schema.columns
|
|
@@ -17036,6 +17067,46 @@ function whereOrEmpty(acc) {
|
|
|
17036
17067
|
return acc.conditions.length ? `WHERE ${acc.conditions.join(" AND ")}` : "";
|
|
17037
17068
|
}
|
|
17038
17069
|
//#endregion
|
|
17070
|
+
//#region src/storage/domains/observability/v-next/preview-compat.ts
|
|
17071
|
+
const generator = coreStorage.buildStoredPreview;
|
|
17072
|
+
const canGeneratePreviews = typeof generator === "function";
|
|
17073
|
+
/** SQL detection only; preview formatting stays in TypeScript. Empty strings are intentional. */
|
|
17074
|
+
function missingPreviewSql(column) {
|
|
17075
|
+
return canGeneratePreviews ? `${column} IS NULL` : "FALSE";
|
|
17076
|
+
}
|
|
17077
|
+
function needsPreview(value) {
|
|
17078
|
+
return canGeneratePreviews && value == null;
|
|
17079
|
+
}
|
|
17080
|
+
function storedPreview(preview, value, serialized) {
|
|
17081
|
+
return canGeneratePreviews ? generator(preview, value, serialized) : preview ?? null;
|
|
17082
|
+
}
|
|
17083
|
+
function readPreview(preview, payload, jsonEncoded = false) {
|
|
17084
|
+
const supplied = preview;
|
|
17085
|
+
if (!canGeneratePreviews) return supplied ?? null;
|
|
17086
|
+
if (supplied != null) return supplied.trim() || null;
|
|
17087
|
+
let value = payload;
|
|
17088
|
+
if (needsPreview(preview) && jsonEncoded && typeof payload === "string") try {
|
|
17089
|
+
value = JSON.parse(payload);
|
|
17090
|
+
} catch {
|
|
17091
|
+
value = null;
|
|
17092
|
+
}
|
|
17093
|
+
return storedPreview(supplied, value, jsonEncoded && typeof payload === "string" ? payload : void 0) || null;
|
|
17094
|
+
}
|
|
17095
|
+
/** Older response builders may shorten previews or turn empty strings into null. */
|
|
17096
|
+
function buildSpanQueryResponse(...args) {
|
|
17097
|
+
if (canGeneratePreviews) return coreStorage.buildSpanQueryResponse(...args);
|
|
17098
|
+
const [plan, selected, payloads, metrics] = args;
|
|
17099
|
+
const response = coreStorage.buildSpanQueryResponse(plan, selected, [], metrics);
|
|
17100
|
+
const byIdentity = new Map(payloads.map((payload) => [coreStorage.spanQueryIdentityKey(payload), payload]));
|
|
17101
|
+
for (const span of response.spans) {
|
|
17102
|
+
const payload = byIdentity.get(coreStorage.spanQueryIdentityKey(span));
|
|
17103
|
+
span.inputPreview = payload?.inputPreview ?? null;
|
|
17104
|
+
span.outputPreview = payload?.outputPreview ?? null;
|
|
17105
|
+
span.errorPreview = payload?.errorPreview ?? null;
|
|
17106
|
+
}
|
|
17107
|
+
return response;
|
|
17108
|
+
}
|
|
17109
|
+
//#endregion
|
|
17039
17110
|
//#region src/storage/domains/observability/v-next/review-status.ts
|
|
17040
17111
|
const FEEDBACK_REVIEW_STATUSES = ["needs-review", "reviewed"];
|
|
17041
17112
|
function isFeedbackReviewStatus(value) {
|
|
@@ -17061,6 +17132,137 @@ function parseUpdateFeedbackReviewStatusArgs(args) {
|
|
|
17061
17132
|
};
|
|
17062
17133
|
}
|
|
17063
17134
|
//#endregion
|
|
17135
|
+
//#region src/storage/domains/observability/v-next/sql.ts
|
|
17136
|
+
/**
|
|
17137
|
+
* SQL helpers for the v-next Postgres observability domain.
|
|
17138
|
+
*
|
|
17139
|
+
* Provides a multi-row INSERT builder with a configurable conflict clause
|
|
17140
|
+
* (`ON CONFLICT DO NOTHING` by default, for retry idempotency), and explicit
|
|
17141
|
+
* jsonb / text[] casts so the pg driver doesn't have to guess column types.
|
|
17142
|
+
*/
|
|
17143
|
+
/**
|
|
17144
|
+
* Encode a JS value for a `$N::jsonb` cast. Always `JSON.stringify` so a
|
|
17145
|
+
* plain string like `"hello"` becomes `"hello"` (a valid JSON scalar) and
|
|
17146
|
+
* not the bare word `hello`, which Postgres rejects when cast to jsonb.
|
|
17147
|
+
*
|
|
17148
|
+
* The result is sanitized because Postgres rejects some sequences that are
|
|
17149
|
+
* legal in JSON: NUL (`\u0000`) fails with 22P05 and unpaired UTF-16
|
|
17150
|
+
* surrogates fail with 22P02. Inserts here are batched into a single
|
|
17151
|
+
* multi-row statement, so one bad value would otherwise reject the whole
|
|
17152
|
+
* batch of observability events.
|
|
17153
|
+
*/
|
|
17154
|
+
function encodeJsonb(value) {
|
|
17155
|
+
if (value === null || value === void 0) return null;
|
|
17156
|
+
if (value instanceof EncodedJsonb) return value.json;
|
|
17157
|
+
return toPgJson(value);
|
|
17158
|
+
}
|
|
17159
|
+
/**
|
|
17160
|
+
* A jsonb value the caller already serialized with `toPgJson`, so `buildInsert`
|
|
17161
|
+
* sends it as is instead of serializing it again. Span writes serialize their
|
|
17162
|
+
* payloads up front because the stored previews are built from that same text.
|
|
17163
|
+
*/
|
|
17164
|
+
var EncodedJsonb = class {
|
|
17165
|
+
json;
|
|
17166
|
+
constructor(json) {
|
|
17167
|
+
this.json = json;
|
|
17168
|
+
}
|
|
17169
|
+
};
|
|
17170
|
+
/** Serialize a value for a jsonb column once, for callers that also need the text. */
|
|
17171
|
+
function encodeJsonbValue(value) {
|
|
17172
|
+
if (value === null || value === void 0) return null;
|
|
17173
|
+
const json = toPgJson(value);
|
|
17174
|
+
return json === void 0 ? null : new EncodedJsonb(json);
|
|
17175
|
+
}
|
|
17176
|
+
/**
|
|
17177
|
+
* Build a multi-row INSERT with explicit column types and ON CONFLICT DO NOTHING.
|
|
17178
|
+
*
|
|
17179
|
+
* @param schema Schema name.
|
|
17180
|
+
* @param table Table name.
|
|
17181
|
+
* @param records Array of records (each is a column-name → value object).
|
|
17182
|
+
* All records must have identical key sets.
|
|
17183
|
+
* @returns { text, values } ready to pass to `client.query`.
|
|
17184
|
+
*/
|
|
17185
|
+
function buildInsert(schema, table, records, onConflict = "ON CONFLICT DO NOTHING") {
|
|
17186
|
+
if (records.length === 0) return null;
|
|
17187
|
+
const columns = Object.keys(records[0]).map((c) => parseSqlIdentifier(c, "column name"));
|
|
17188
|
+
const quotedColumns = columns.map((c) => `"${c}"`).join(", ");
|
|
17189
|
+
const values = [];
|
|
17190
|
+
const rowPlaceholders = [];
|
|
17191
|
+
let p = 1;
|
|
17192
|
+
for (const record of records) {
|
|
17193
|
+
const placeholders = columns.map((col) => {
|
|
17194
|
+
const raw = record[col];
|
|
17195
|
+
if (JSONB_COLUMNS.has(col)) {
|
|
17196
|
+
values.push(encodeJsonb(raw));
|
|
17197
|
+
return `$${p++}::jsonb`;
|
|
17198
|
+
}
|
|
17199
|
+
if (TEXT_ARRAY_COLUMNS.has(col)) {
|
|
17200
|
+
values.push(Array.isArray(raw) ? raw : []);
|
|
17201
|
+
return `$${p++}::text[]`;
|
|
17202
|
+
}
|
|
17203
|
+
values.push(raw === void 0 ? null : raw);
|
|
17204
|
+
return `$${p++}`;
|
|
17205
|
+
});
|
|
17206
|
+
rowPlaceholders.push(`(${placeholders.join(", ")})`);
|
|
17207
|
+
}
|
|
17208
|
+
return {
|
|
17209
|
+
text: `INSERT INTO ${qualifiedTable(schema, table)} (${quotedColumns})
|
|
17210
|
+
VALUES ${rowPlaceholders.join(", ")}
|
|
17211
|
+
${onConflict}`,
|
|
17212
|
+
values
|
|
17213
|
+
};
|
|
17214
|
+
}
|
|
17215
|
+
/** Primary key of the span table; also the conflict target for span upserts. */
|
|
17216
|
+
const SPAN_KEY_COLUMNS = [
|
|
17217
|
+
"traceId",
|
|
17218
|
+
"spanId",
|
|
17219
|
+
"endedAt"
|
|
17220
|
+
];
|
|
17221
|
+
/**
|
|
17222
|
+
* Conflict clause for span writes.
|
|
17223
|
+
*
|
|
17224
|
+
* A span is written twice under the event-sourced strategy: once when it starts
|
|
17225
|
+
* (`isPending = true`, `endedAt` synthesized from `startedAt`) and once when it
|
|
17226
|
+
* ends. Those two rows normally differ in `endedAt` and so coexist under the
|
|
17227
|
+
* primary key. A zero-duration span is the exception — its real `endedAt`
|
|
17228
|
+
* equals its `startedAt`, so the end row collides with the pending row. Letting
|
|
17229
|
+
* `DO NOTHING` win there would strand the span as permanently running, so the
|
|
17230
|
+
* end row overwrites the pending one instead.
|
|
17231
|
+
*
|
|
17232
|
+
* The `WHERE` guard keeps the clause a no-op for every other conflict, which
|
|
17233
|
+
* preserves insert-only retry idempotency: a replayed end row cannot clobber
|
|
17234
|
+
* the row it duplicates, and a late-arriving start row cannot revert an ended
|
|
17235
|
+
* span to pending.
|
|
17236
|
+
*/
|
|
17237
|
+
function spanConflictClause(columns) {
|
|
17238
|
+
return `ON CONFLICT ("traceId", "spanId", "endedAt") DO UPDATE SET ${columns.filter((column) => !SPAN_KEY_COLUMNS.includes(column)).map((column) => `"${column}" = EXCLUDED."${column}"`).join(", ")}
|
|
17239
|
+
WHERE ${TABLE_SPAN_EVENTS}."isPending" AND NOT EXCLUDED."isPending"`;
|
|
17240
|
+
}
|
|
17241
|
+
/**
|
|
17242
|
+
* Ordering that picks the winning row for a span under the event-sourced write
|
|
17243
|
+
* model. An ended row always beats a pending row for the same span; among rows
|
|
17244
|
+
* of the same kind the latest write wins, with `cursorId` breaking ties between
|
|
17245
|
+
* rows that share a timestamp. Combine with `DISTINCT ON` (or `LIMIT 1`) after
|
|
17246
|
+
* the grouping key to collapse a span's rows down to one.
|
|
17247
|
+
*/
|
|
17248
|
+
const SPAN_COLLAPSE_ORDER = "\"isPending\" ASC, \"endedAt\" DESC, \"cursorId\" DESC";
|
|
17249
|
+
/**
|
|
17250
|
+
* Standard SELECT column list for tracing tables. The select projects every
|
|
17251
|
+
* column the row→record converters expect.
|
|
17252
|
+
*/
|
|
17253
|
+
const SPAN_SELECT_COLUMNS = `
|
|
17254
|
+
${buildSelectColumns(SPAN_EVENT_COLUMNS)}`;
|
|
17255
|
+
const SPAN_LIGHT_SELECT_COLUMNS = `
|
|
17256
|
+
${buildNamedSelectColumns(SPAN_LIGHT_SELECT_COLUMN_NAMES)}`;
|
|
17257
|
+
const METRIC_SELECT_COLUMNS = `
|
|
17258
|
+
${buildSelectColumns(METRIC_EVENT_COLUMNS)}`;
|
|
17259
|
+
const LOG_SELECT_COLUMNS = `
|
|
17260
|
+
${buildSelectColumns(LOG_EVENT_COLUMNS)}`;
|
|
17261
|
+
const SCORE_SELECT_COLUMNS = `
|
|
17262
|
+
${buildSelectColumns(SCORE_EVENT_COLUMNS)}`;
|
|
17263
|
+
const FEEDBACK_SELECT_COLUMNS = `
|
|
17264
|
+
${buildSelectColumns(FEEDBACK_EVENT_COLUMNS)}`;
|
|
17265
|
+
//#endregion
|
|
17064
17266
|
//#region src/storage/domains/observability/v-next/helpers.ts
|
|
17065
17267
|
const PROMOTED_KEYS = /* @__PURE__ */ new Set([
|
|
17066
17268
|
"experimentId",
|
|
@@ -17210,10 +17412,26 @@ function commonContextToRow(record) {
|
|
|
17210
17412
|
serviceName: record.serviceName ?? null
|
|
17211
17413
|
};
|
|
17212
17414
|
}
|
|
17415
|
+
/**
|
|
17416
|
+
* Serialize a span payload for its jsonb column and build its stored preview from the same value.
|
|
17417
|
+
* Nonblank upstream previews win (trimmed and capped like generated ones). The preview goes into a `text` column, which
|
|
17418
|
+
* rejects NUL, so it gets the same repair `toPgJson` applies to the payload.
|
|
17419
|
+
*/
|
|
17420
|
+
function spanPayload(value, upstream) {
|
|
17421
|
+
const encoded = encodeJsonbValue(value);
|
|
17422
|
+
const preview = storedPreview(upstream, value, encoded?.json);
|
|
17423
|
+
return {
|
|
17424
|
+
encoded,
|
|
17425
|
+
preview: preview === null ? null : toPgText(preview)
|
|
17426
|
+
};
|
|
17427
|
+
}
|
|
17213
17428
|
function spanRecordToRow(span) {
|
|
17214
17429
|
const isPending = !span.isEvent && span.endedAt == null;
|
|
17215
17430
|
const endedAt = span.isEvent ? span.startedAt : span.endedAt ?? span.startedAt;
|
|
17216
17431
|
const metadata = span.metadata ?? null;
|
|
17432
|
+
const input = spanPayload(span.input, span.inputPreview);
|
|
17433
|
+
const output = spanPayload(span.output, span.outputPreview);
|
|
17434
|
+
const error = spanPayload(span.error, span.errorPreview);
|
|
17217
17435
|
return {
|
|
17218
17436
|
...commonContextToRow(span),
|
|
17219
17437
|
traceId: span.traceId,
|
|
@@ -17231,10 +17449,13 @@ function spanRecordToRow(span) {
|
|
|
17231
17449
|
scope: jsonField(span.scope),
|
|
17232
17450
|
attributes: jsonField(span.attributes),
|
|
17233
17451
|
links: jsonField(span.links),
|
|
17234
|
-
input:
|
|
17235
|
-
output:
|
|
17236
|
-
error:
|
|
17237
|
-
requestContext: jsonField(span.requestContext)
|
|
17452
|
+
input: input.encoded,
|
|
17453
|
+
output: output.encoded,
|
|
17454
|
+
error: error.encoded,
|
|
17455
|
+
requestContext: jsonField(span.requestContext),
|
|
17456
|
+
inputPreview: input.preview,
|
|
17457
|
+
outputPreview: output.preview,
|
|
17458
|
+
errorPreview: error.preview
|
|
17238
17459
|
};
|
|
17239
17460
|
}
|
|
17240
17461
|
/**
|
|
@@ -17301,6 +17522,32 @@ function rowToLightSpanRecord(row) {
|
|
|
17301
17522
|
updatedAt: null
|
|
17302
17523
|
};
|
|
17303
17524
|
}
|
|
17525
|
+
/** A stored preview as lists return it: `''` (nothing to preview) and NULL become null. */
|
|
17526
|
+
function listPreview(value) {
|
|
17527
|
+
return readPreview(value);
|
|
17528
|
+
}
|
|
17529
|
+
/**
|
|
17530
|
+
* Build a trace-list {@link LightSpanRecord} from a row that projected the light trace-list columns
|
|
17531
|
+
* (see `TRACE_LIGHT_SELECT_COLUMNS` in traces.ts): the timeline fields plus the memory context,
|
|
17532
|
+
* metadata and the stored previews. Callers fill in previews for rows written before they were
|
|
17533
|
+
* stored first.
|
|
17534
|
+
*/
|
|
17535
|
+
function rowToLightTraceRecord(row) {
|
|
17536
|
+
const record = rowToLightSpanRecord(row);
|
|
17537
|
+
return {
|
|
17538
|
+
...record,
|
|
17539
|
+
status: computeTraceStatus({
|
|
17540
|
+
error: record.error,
|
|
17541
|
+
endedAt: record.endedAt
|
|
17542
|
+
}),
|
|
17543
|
+
threadId: row.threadId == null ? null : String(row.threadId),
|
|
17544
|
+
resourceId: row.resourceId == null ? null : String(row.resourceId),
|
|
17545
|
+
metadata: parsedJson(row.metadataRaw) ?? void 0,
|
|
17546
|
+
inputPreview: listPreview(row.inputPreview),
|
|
17547
|
+
outputPreview: listPreview(row.outputPreview),
|
|
17548
|
+
errorPreview: listPreview(row.errorPreview)
|
|
17549
|
+
};
|
|
17550
|
+
}
|
|
17304
17551
|
function logRecordToRow(log) {
|
|
17305
17552
|
return {
|
|
17306
17553
|
...commonContextToRow(log),
|
|
@@ -17816,119 +18063,6 @@ const COMPLEX_GROUP_BY_EXCLUDED = /* @__PURE__ */ new Set([
|
|
|
17816
18063
|
"requestContext"
|
|
17817
18064
|
]);
|
|
17818
18065
|
//#endregion
|
|
17819
|
-
//#region src/storage/domains/observability/v-next/sql.ts
|
|
17820
|
-
/**
|
|
17821
|
-
* SQL helpers for the v-next Postgres observability domain.
|
|
17822
|
-
*
|
|
17823
|
-
* Provides a multi-row INSERT builder with a configurable conflict clause
|
|
17824
|
-
* (`ON CONFLICT DO NOTHING` by default, for retry idempotency), and explicit
|
|
17825
|
-
* jsonb / text[] casts so the pg driver doesn't have to guess column types.
|
|
17826
|
-
*/
|
|
17827
|
-
/**
|
|
17828
|
-
* Encode a JS value for a `$N::jsonb` cast. Always `JSON.stringify` so a
|
|
17829
|
-
* plain string like `"hello"` becomes `"hello"` (a valid JSON scalar) and
|
|
17830
|
-
* not the bare word `hello`, which Postgres rejects when cast to jsonb.
|
|
17831
|
-
*
|
|
17832
|
-
* The result is sanitized because Postgres rejects some sequences that are
|
|
17833
|
-
* legal in JSON: NUL (`\u0000`) fails with 22P05 and unpaired UTF-16
|
|
17834
|
-
* surrogates fail with 22P02. Inserts here are batched into a single
|
|
17835
|
-
* multi-row statement, so one bad value would otherwise reject the whole
|
|
17836
|
-
* batch of observability events.
|
|
17837
|
-
*/
|
|
17838
|
-
function encodeJsonb(value) {
|
|
17839
|
-
if (value === null || value === void 0) return null;
|
|
17840
|
-
return toPgJson(value);
|
|
17841
|
-
}
|
|
17842
|
-
/**
|
|
17843
|
-
* Build a multi-row INSERT with explicit column types and ON CONFLICT DO NOTHING.
|
|
17844
|
-
*
|
|
17845
|
-
* @param schema Schema name.
|
|
17846
|
-
* @param table Table name.
|
|
17847
|
-
* @param records Array of records (each is a column-name → value object).
|
|
17848
|
-
* All records must have identical key sets.
|
|
17849
|
-
* @returns { text, values } ready to pass to `client.query`.
|
|
17850
|
-
*/
|
|
17851
|
-
function buildInsert(schema, table, records, onConflict = "ON CONFLICT DO NOTHING") {
|
|
17852
|
-
if (records.length === 0) return null;
|
|
17853
|
-
const columns = Object.keys(records[0]).map((c) => parseSqlIdentifier(c, "column name"));
|
|
17854
|
-
const quotedColumns = columns.map((c) => `"${c}"`).join(", ");
|
|
17855
|
-
const values = [];
|
|
17856
|
-
const rowPlaceholders = [];
|
|
17857
|
-
let p = 1;
|
|
17858
|
-
for (const record of records) {
|
|
17859
|
-
const placeholders = columns.map((col) => {
|
|
17860
|
-
const raw = record[col];
|
|
17861
|
-
if (JSONB_COLUMNS.has(col)) {
|
|
17862
|
-
values.push(encodeJsonb(raw));
|
|
17863
|
-
return `$${p++}::jsonb`;
|
|
17864
|
-
}
|
|
17865
|
-
if (TEXT_ARRAY_COLUMNS.has(col)) {
|
|
17866
|
-
values.push(Array.isArray(raw) ? raw : []);
|
|
17867
|
-
return `$${p++}::text[]`;
|
|
17868
|
-
}
|
|
17869
|
-
values.push(raw === void 0 ? null : raw);
|
|
17870
|
-
return `$${p++}`;
|
|
17871
|
-
});
|
|
17872
|
-
rowPlaceholders.push(`(${placeholders.join(", ")})`);
|
|
17873
|
-
}
|
|
17874
|
-
return {
|
|
17875
|
-
text: `INSERT INTO ${qualifiedTable(schema, table)} (${quotedColumns})
|
|
17876
|
-
VALUES ${rowPlaceholders.join(", ")}
|
|
17877
|
-
${onConflict}`,
|
|
17878
|
-
values
|
|
17879
|
-
};
|
|
17880
|
-
}
|
|
17881
|
-
/** Primary key of the span table; also the conflict target for span upserts. */
|
|
17882
|
-
const SPAN_KEY_COLUMNS = [
|
|
17883
|
-
"traceId",
|
|
17884
|
-
"spanId",
|
|
17885
|
-
"endedAt"
|
|
17886
|
-
];
|
|
17887
|
-
/**
|
|
17888
|
-
* Conflict clause for span writes.
|
|
17889
|
-
*
|
|
17890
|
-
* A span is written twice under the event-sourced strategy: once when it starts
|
|
17891
|
-
* (`isPending = true`, `endedAt` synthesized from `startedAt`) and once when it
|
|
17892
|
-
* ends. Those two rows normally differ in `endedAt` and so coexist under the
|
|
17893
|
-
* primary key. A zero-duration span is the exception — its real `endedAt`
|
|
17894
|
-
* equals its `startedAt`, so the end row collides with the pending row. Letting
|
|
17895
|
-
* `DO NOTHING` win there would strand the span as permanently running, so the
|
|
17896
|
-
* end row overwrites the pending one instead.
|
|
17897
|
-
*
|
|
17898
|
-
* The `WHERE` guard keeps the clause a no-op for every other conflict, which
|
|
17899
|
-
* preserves insert-only retry idempotency: a replayed end row cannot clobber
|
|
17900
|
-
* the row it duplicates, and a late-arriving start row cannot revert an ended
|
|
17901
|
-
* span to pending.
|
|
17902
|
-
*/
|
|
17903
|
-
function spanConflictClause(columns) {
|
|
17904
|
-
return `ON CONFLICT ("traceId", "spanId", "endedAt") DO UPDATE SET ${columns.filter((column) => !SPAN_KEY_COLUMNS.includes(column)).map((column) => `"${column}" = EXCLUDED."${column}"`).join(", ")}
|
|
17905
|
-
WHERE ${TABLE_SPAN_EVENTS}."isPending" AND NOT EXCLUDED."isPending"`;
|
|
17906
|
-
}
|
|
17907
|
-
/**
|
|
17908
|
-
* Ordering that picks the winning row for a span under the event-sourced write
|
|
17909
|
-
* model. An ended row always beats a pending row for the same span; among rows
|
|
17910
|
-
* of the same kind the latest write wins, with `cursorId` breaking ties between
|
|
17911
|
-
* rows that share a timestamp. Combine with `DISTINCT ON` (or `LIMIT 1`) after
|
|
17912
|
-
* the grouping key to collapse a span's rows down to one.
|
|
17913
|
-
*/
|
|
17914
|
-
const SPAN_COLLAPSE_ORDER = "\"isPending\" ASC, \"endedAt\" DESC, \"cursorId\" DESC";
|
|
17915
|
-
/**
|
|
17916
|
-
* Standard SELECT column list for tracing tables. The select projects every
|
|
17917
|
-
* column the row→record converters expect.
|
|
17918
|
-
*/
|
|
17919
|
-
const SPAN_SELECT_COLUMNS = `
|
|
17920
|
-
${buildSelectColumns(SPAN_EVENT_COLUMNS)}`;
|
|
17921
|
-
const SPAN_LIGHT_SELECT_COLUMNS = `
|
|
17922
|
-
${buildNamedSelectColumns(SPAN_LIGHT_SELECT_COLUMN_NAMES)}`;
|
|
17923
|
-
const METRIC_SELECT_COLUMNS = `
|
|
17924
|
-
${buildSelectColumns(METRIC_EVENT_COLUMNS)}`;
|
|
17925
|
-
const LOG_SELECT_COLUMNS = `
|
|
17926
|
-
${buildSelectColumns(LOG_EVENT_COLUMNS)}`;
|
|
17927
|
-
const SCORE_SELECT_COLUMNS = `
|
|
17928
|
-
${buildSelectColumns(SCORE_EVENT_COLUMNS)}`;
|
|
17929
|
-
const FEEDBACK_SELECT_COLUMNS = `
|
|
17930
|
-
${buildSelectColumns(FEEDBACK_EVENT_COLUMNS)}`;
|
|
17931
|
-
//#endregion
|
|
17932
18066
|
//#region src/storage/domains/observability/v-next/feedback.ts
|
|
17933
18067
|
/**
|
|
17934
18068
|
* Feedback operations for the v-next Postgres observability domain.
|
|
@@ -19087,7 +19221,10 @@ const TRACE_SELECT = `
|
|
|
19087
19221
|
r."entityId" AS "entityId",
|
|
19088
19222
|
r."parentSpanId" AS "parentSpanId",
|
|
19089
19223
|
r."metadataRaw" AS "metadata",
|
|
19090
|
-
r."
|
|
19224
|
+
r."inputPreview" AS "inputPreview",
|
|
19225
|
+
r."outputPreview" AS "outputPreview",
|
|
19226
|
+
r."errorPreview" AS "errorPreview",
|
|
19227
|
+
r."cursorId" AS "cursorId",
|
|
19091
19228
|
r."threadId" AS "threadId",
|
|
19092
19229
|
r."resourceId" AS "resourceId",
|
|
19093
19230
|
r."startedAt" AS "startedAt",
|
|
@@ -19545,7 +19682,7 @@ function compilePostgresTraceQuery(schema, plan, mode = "data", safeHorizon) {
|
|
|
19545
19682
|
safeHorizon
|
|
19546
19683
|
};
|
|
19547
19684
|
}
|
|
19548
|
-
const { ctes, values } = compilePostgresTraceCandidates(schema, plan, `${TRACE_SELECT}${plan.paginationMode === "delta" ? ", r.\"xactId\"
|
|
19685
|
+
const { ctes, values } = compilePostgresTraceCandidates(schema, plan, `${TRACE_SELECT}${plan.paginationMode === "delta" ? ", r.\"xactId\"" : ""}`, deltaWindow);
|
|
19549
19686
|
const candidates = `WITH ${ctes.join(",\n")}`;
|
|
19550
19687
|
if (plan.paginationMode === "page" && mode === "count") return {
|
|
19551
19688
|
text: `${candidates}
|
|
@@ -19787,6 +19924,34 @@ async function setRemainingTimeout(transaction, deadline) {
|
|
|
19787
19924
|
if (remainingTimeoutMs <= 0) throw new coreStorage.TraceQueryExecutionError();
|
|
19788
19925
|
await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
|
|
19789
19926
|
}
|
|
19927
|
+
const PREVIEW_PAYLOADS$2 = [
|
|
19928
|
+
["inputPreview", "input"],
|
|
19929
|
+
["outputPreview", "output"],
|
|
19930
|
+
["errorPreview", "error"]
|
|
19931
|
+
];
|
|
19932
|
+
/**
|
|
19933
|
+
* Fill the previews of root rows written before the preview columns existed (stored preview NULL)
|
|
19934
|
+
* by reading just those rows' payloads, and only the payloads whose preview is missing. Rows are
|
|
19935
|
+
* keyed by `cursorId`, which identifies the exact root row the list picked.
|
|
19936
|
+
*
|
|
19937
|
+
* jsonb comes back decoded, so the preview is built with `buildPreview(value)`. jsonb also
|
|
19938
|
+
* normalizes object key order, so a compact-JSON fallback preview can differ from the one the
|
|
19939
|
+
* writer would have stored from the original value. That is acceptable for these old rows.
|
|
19940
|
+
*/
|
|
19941
|
+
async function fillMissingTracePreviews(transaction, schema, rows, deadline) {
|
|
19942
|
+
const missing = rows.filter((row) => PREVIEW_PAYLOADS$2.some(([preview]) => needsPreview(row[preview])));
|
|
19943
|
+
if (!missing.length) return;
|
|
19944
|
+
await setRemainingTimeout(transaction, deadline);
|
|
19945
|
+
const payloads = await transaction.any(`SELECT e."cursorId"::text AS "cursorId",
|
|
19946
|
+
${PREVIEW_PAYLOADS$2.map(([preview, payload]) => `${missing.some((row) => needsPreview(row[preview])) ? `e."${payload}"` : "NULL"} AS "${payload}"`).join(",\n ")}
|
|
19947
|
+
FROM ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} e
|
|
19948
|
+
WHERE (e."traceId", e."cursorId") IN (SELECT * FROM unnest($1::text[], $2::bigint[]))`, [missing.map((row) => String(row.traceId)), missing.map((row) => String(row.cursorId))]);
|
|
19949
|
+
const byCursor = new Map(payloads.map((payload) => [String(payload.cursorId), payload]));
|
|
19950
|
+
for (const row of missing) {
|
|
19951
|
+
const payload = byCursor.get(String(row.cursorId));
|
|
19952
|
+
for (const [preview, column] of PREVIEW_PAYLOADS$2) if (needsPreview(row[preview])) row[preview] = readPreview(row[preview], payload?.[column]);
|
|
19953
|
+
}
|
|
19954
|
+
}
|
|
19790
19955
|
function traceRowToResult(row) {
|
|
19791
19956
|
return {
|
|
19792
19957
|
traceId: String(row.traceId),
|
|
@@ -19796,7 +19961,9 @@ function traceRowToResult(row) {
|
|
|
19796
19961
|
parentSpanId: row.parentSpanId ?? null,
|
|
19797
19962
|
createdAt: asIsoTimestamp$1(row.startedAt),
|
|
19798
19963
|
metadata: row.metadata ?? null,
|
|
19799
|
-
inputPreview:
|
|
19964
|
+
inputPreview: readPreview(row.inputPreview),
|
|
19965
|
+
outputPreview: readPreview(row.outputPreview),
|
|
19966
|
+
errorPreview: readPreview(row.errorPreview),
|
|
19800
19967
|
threadId: row.threadId == null ? null : String(row.threadId),
|
|
19801
19968
|
resourceId: row.resourceId == null ? null : String(row.resourceId),
|
|
19802
19969
|
startedAt: asIsoTimestamp$1(row.startedAt),
|
|
@@ -19824,6 +19991,7 @@ async function queryTraces(client, schema, plan, timeoutMs) {
|
|
|
19824
19991
|
rows = await transaction.any(query.text, query.values);
|
|
19825
19992
|
}
|
|
19826
19993
|
const visible = rows.slice(0, plan.limit);
|
|
19994
|
+
await fillMissingTracePreviews(transaction, schema, visible, deadline);
|
|
19827
19995
|
const last = visible.at(-1);
|
|
19828
19996
|
return coreStorage.traceQueryResponseSchema.parse({
|
|
19829
19997
|
traces: visible.map(traceRowToResult),
|
|
@@ -19853,6 +20021,7 @@ async function queryTraces(client, schema, plan, timeoutMs) {
|
|
|
19853
20021
|
if (remainingTimeoutMs <= 0) throw new coreStorage.TraceQueryExecutionError();
|
|
19854
20022
|
await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
|
|
19855
20023
|
const rows = await transaction.any(dataQuery.text, dataQuery.values);
|
|
20024
|
+
await fillMissingTracePreviews(transaction, schema, rows, deadline);
|
|
19856
20025
|
return {
|
|
19857
20026
|
total: Number(countRows[0]?.count ?? 0),
|
|
19858
20027
|
rows,
|
|
@@ -19872,7 +20041,12 @@ async function queryTraces(client, schema, plan, timeoutMs) {
|
|
|
19872
20041
|
});
|
|
19873
20042
|
}
|
|
19874
20043
|
const query = compilePostgresTraceQuery(schema, plan);
|
|
19875
|
-
const
|
|
20044
|
+
const deadline = performance.now() + coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
20045
|
+
const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, async (transaction) => {
|
|
20046
|
+
const rows = await transaction.any(query.text, query.values);
|
|
20047
|
+
if (plan.result === "traces") await fillMissingTracePreviews(transaction, schema, rows.slice(0, plan.limit), deadline);
|
|
20048
|
+
return rows;
|
|
20049
|
+
});
|
|
19876
20050
|
const visibleRows = rows.slice(0, plan.limit);
|
|
19877
20051
|
if (plan.result === "groups") {
|
|
19878
20052
|
const groups = visibleRows.map((row) => ({ threadId: String(row.threadId) }));
|
|
@@ -20029,6 +20203,29 @@ function compilePostgresSpanQuery(schema, plan) {
|
|
|
20029
20203
|
values
|
|
20030
20204
|
};
|
|
20031
20205
|
}
|
|
20206
|
+
const PREVIEW_PAYLOADS$1 = [
|
|
20207
|
+
["inputPreview", "input"],
|
|
20208
|
+
["outputPreview", "output"],
|
|
20209
|
+
["errorPreview", "error"]
|
|
20210
|
+
];
|
|
20211
|
+
/**
|
|
20212
|
+
* Stored previews of a hydrated row, built from the payload where the stored preview is NULL.
|
|
20213
|
+
* jsonb comes back decoded, so the fallback uses `buildPreview(value)`. jsonb normalizes object key
|
|
20214
|
+
* order, so a compact-JSON fallback preview can differ from what the writer would have stored from
|
|
20215
|
+
* the original value; that is acceptable for rows written before the preview columns existed.
|
|
20216
|
+
*/
|
|
20217
|
+
function toSpanQueryPayload(row) {
|
|
20218
|
+
const [inputPreview, outputPreview, errorPreview] = PREVIEW_PAYLOADS$1.map(([preview, payload]) => readPreview(row[preview], row[payload]));
|
|
20219
|
+
return {
|
|
20220
|
+
organizationId: row.organizationId ?? null,
|
|
20221
|
+
resourceId: row.resourceId ?? null,
|
|
20222
|
+
traceId: String(row.traceId),
|
|
20223
|
+
spanId: String(row.spanId),
|
|
20224
|
+
inputPreview,
|
|
20225
|
+
outputPreview,
|
|
20226
|
+
errorPreview
|
|
20227
|
+
};
|
|
20228
|
+
}
|
|
20032
20229
|
async function querySpans(client, schema, plan, timeoutMs) {
|
|
20033
20230
|
const deadline = performance.now() + coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
20034
20231
|
return runWithPostgresTraceQueryTimeout(client, timeoutMs, async (tx) => {
|
|
@@ -20053,9 +20250,9 @@ async function querySpans(client, schema, plan, timeoutMs) {
|
|
|
20053
20250
|
payloadValues.push(row.version);
|
|
20054
20251
|
return `(${key} AND "cursorId" = $${payloadValues.length}::bigint)`;
|
|
20055
20252
|
}).join(" OR ");
|
|
20056
|
-
const payloads = await tx.any(`SELECT ${identities},
|
|
20057
|
-
|
|
20058
|
-
FROM ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} WHERE ${payloadWhere}`, payloadValues);
|
|
20253
|
+
const payloads = (await tx.any(`SELECT ${identities},
|
|
20254
|
+
${PREVIEW_PAYLOADS$1.map(([preview, payload]) => `"${preview}", CASE WHEN ${missingPreviewSql(`"${preview}"`)} THEN "${payload}" END AS "${payload}"`).join(",\n ")}
|
|
20255
|
+
FROM ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} WHERE ${payloadWhere}`, payloadValues)).map(toSpanQueryPayload);
|
|
20059
20256
|
await remaining();
|
|
20060
20257
|
const metricValues = [];
|
|
20061
20258
|
const metricWhere = selectedKeys(page, metricValues);
|
|
@@ -20066,7 +20263,7 @@ async function querySpans(client, schema, plan, timeoutMs) {
|
|
|
20066
20263
|
ORDER BY ${identities}, "metricId", "cursorId" DESC) m
|
|
20067
20264
|
LIMIT ${coreStorage.SPAN_QUERY_MAX_COST_METRICS + 1}`, metricValues);
|
|
20068
20265
|
if (metrics.length > coreStorage.SPAN_QUERY_MAX_COST_METRICS) throw new coreStorage.TraceQueryResourceLimitError();
|
|
20069
|
-
return
|
|
20266
|
+
return buildSpanQueryResponse(plan, selected, payloads, metrics);
|
|
20070
20267
|
}, { repeatableRead: true });
|
|
20071
20268
|
}
|
|
20072
20269
|
//#endregion
|
|
@@ -20531,16 +20728,73 @@ function buildListTracesFilters(filters, spanTable, nextParamIdx) {
|
|
|
20531
20728
|
* array-of-strings representation here.
|
|
20532
20729
|
*/
|
|
20533
20730
|
const SPAN_SELECT_COLUMNS_ALIASED = SPAN_SELECT_COLUMNS.replace(/\n/g, " ").split(",").map((c) => `r.${c.trim()}`).join(", ");
|
|
20731
|
+
const FULL_PROJECTION = {
|
|
20732
|
+
columns: SPAN_SELECT_COLUMNS_ALIASED,
|
|
20733
|
+
mapRows: (_client, _span, rows) => toTraceSpans(rows.map(rowToSpanRecord))
|
|
20734
|
+
};
|
|
20735
|
+
/**
|
|
20736
|
+
* Columns a light trace list renders: the timeline fields, memory context, metadata and the stored
|
|
20737
|
+
* previews. The `input` / `output` payloads stay off the read path; rows written before the
|
|
20738
|
+
* previews were stored get theirs from the payloads, read for just those rows.
|
|
20739
|
+
*/
|
|
20740
|
+
const TRACE_LIGHT_SELECT_COLUMNS = [
|
|
20741
|
+
...SPAN_LIGHT_SELECT_COLUMN_NAMES,
|
|
20742
|
+
"threadId",
|
|
20743
|
+
"resourceId",
|
|
20744
|
+
"metadataRaw",
|
|
20745
|
+
"inputPreview",
|
|
20746
|
+
"outputPreview",
|
|
20747
|
+
"errorPreview"
|
|
20748
|
+
].map((column) => `r."${column}"`).join(", ");
|
|
20749
|
+
const PREVIEW_PAYLOADS = [
|
|
20750
|
+
["inputPreview", "input"],
|
|
20751
|
+
["outputPreview", "output"],
|
|
20752
|
+
["errorPreview", "error"]
|
|
20753
|
+
];
|
|
20754
|
+
/**
|
|
20755
|
+
* Builds the previews of rows written before previews were stored (NULL preview) from those rows'
|
|
20756
|
+
* payloads. Only the flagged rows' payloads are read, so this fades out as old rows age out.
|
|
20757
|
+
*
|
|
20758
|
+
* `jsonb` comes back decoded with its keys reordered, so a compact-JSON preview built here can
|
|
20759
|
+
* differ in key order from the one the writer would have stored. Acceptable for old rows.
|
|
20760
|
+
*/
|
|
20761
|
+
async function fillMissingPreviews(client, span, rows) {
|
|
20762
|
+
const missing = rows.filter((row) => PREVIEW_PAYLOADS.some(([preview]) => needsPreview(row[preview])));
|
|
20763
|
+
if (!missing.length) return;
|
|
20764
|
+
const payloads = await client.manyOrNone(`SELECT e."cursorId"::text AS "cursorId",
|
|
20765
|
+
${PREVIEW_PAYLOADS.map(([preview, payload]) => `${missing.some((row) => needsPreview(row[preview])) ? `e."${payload}"` : "NULL"} AS "${payload}"`).join(",\n ")}
|
|
20766
|
+
FROM ${span} e
|
|
20767
|
+
WHERE (e."traceId", e."cursorId") IN (SELECT * FROM unnest($1::text[], $2::bigint[]))`, [missing.map((row) => String(row.traceId)), missing.map((row) => String(row.cursorId))]);
|
|
20768
|
+
const byCursor = new Map(payloads.map((payload) => [String(payload.cursorId), payload]));
|
|
20769
|
+
for (const row of missing) {
|
|
20770
|
+
const payload = byCursor.get(String(row.cursorId));
|
|
20771
|
+
for (const [preview, column] of PREVIEW_PAYLOADS) if (needsPreview(row[preview])) row[preview] = readPreview(row[preview], payload?.[column]);
|
|
20772
|
+
}
|
|
20773
|
+
}
|
|
20774
|
+
const LIGHT_PROJECTION = {
|
|
20775
|
+
columns: TRACE_LIGHT_SELECT_COLUMNS,
|
|
20776
|
+
mapRows: async (client, span, rows) => {
|
|
20777
|
+
await fillMissingPreviews(client, span, rows);
|
|
20778
|
+
return rows.map(rowToLightTraceRecord);
|
|
20779
|
+
}
|
|
20780
|
+
};
|
|
20534
20781
|
async function listTraces(client, schema, args) {
|
|
20782
|
+
return listTraceRows(client, schema, args, FULL_PROJECTION);
|
|
20783
|
+
}
|
|
20784
|
+
/** Trace list projecting only the columns a list renders, with stored previews instead of payloads. */
|
|
20785
|
+
async function listTracesLight(client, schema, args) {
|
|
20786
|
+
return listTraceRows(client, schema, args, LIGHT_PROJECTION);
|
|
20787
|
+
}
|
|
20788
|
+
async function listTraceRows(client, schema, args, projection) {
|
|
20535
20789
|
const { mode, filters, pagination, orderBy, after, limit } = listTracesArgsSchema.parse(args);
|
|
20536
20790
|
const span = qualifiedTable(schema, TABLE_SPAN_EVENTS);
|
|
20537
20791
|
if (mode === "delta") {
|
|
20538
20792
|
assertDeltaPollingEnabled();
|
|
20539
|
-
return listTracesDelta(client, span, filters, after, limit);
|
|
20793
|
+
return listTracesDelta(client, span, projection, filters, after, limit);
|
|
20540
20794
|
}
|
|
20541
|
-
return listTracesPage(client, span, filters, pagination.page, pagination.perPage, orderBy.field, orderBy.direction);
|
|
20795
|
+
return listTracesPage(client, span, projection, filters, pagination.page, pagination.perPage, orderBy.field, orderBy.direction);
|
|
20542
20796
|
}
|
|
20543
|
-
async function listTracesPage(client, span, filters, page, perPage, orderField, orderDir) {
|
|
20797
|
+
async function listTracesPage(client, span, projection, filters, page, perPage, orderField, orderDir) {
|
|
20544
20798
|
const { conditions, params, nextParamIdx } = buildListTracesFilters(filters, span, 1);
|
|
20545
20799
|
let i = nextParamIdx;
|
|
20546
20800
|
const whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
@@ -20548,15 +20802,18 @@ async function listTracesPage(client, span, filters, page, perPage, orderField,
|
|
|
20548
20802
|
const countRow = await client.oneOrNone(`SELECT COUNT(*)::text AS count FROM ${span} r ${whereClause}`, params);
|
|
20549
20803
|
const count = Number(countRow?.count ?? 0);
|
|
20550
20804
|
let spans = [];
|
|
20551
|
-
if (count > 0)
|
|
20805
|
+
if (count > 0) {
|
|
20806
|
+
const rows = await client.manyOrNone(`SELECT ${projection.columns}
|
|
20552
20807
|
FROM ${span} r
|
|
20553
20808
|
${whereClause}
|
|
20554
20809
|
${orderClause}
|
|
20555
20810
|
LIMIT $${i++} OFFSET $${i++}`, [
|
|
20556
|
-
|
|
20557
|
-
|
|
20558
|
-
|
|
20559
|
-
|
|
20811
|
+
...params,
|
|
20812
|
+
perPage,
|
|
20813
|
+
page * perPage
|
|
20814
|
+
]);
|
|
20815
|
+
spans = await projection.mapRows(client, span, rows);
|
|
20816
|
+
}
|
|
20560
20817
|
const deltaCursor = deltaPollingFeatureEnabled() ? await readTracesStreamHeadCursor(client, span, filters) : void 0;
|
|
20561
20818
|
return {
|
|
20562
20819
|
pagination: {
|
|
@@ -20565,11 +20822,11 @@ async function listTracesPage(client, span, filters, page, perPage, orderField,
|
|
|
20565
20822
|
perPage,
|
|
20566
20823
|
hasMore: (page + 1) * perPage < count
|
|
20567
20824
|
},
|
|
20568
|
-
spans
|
|
20825
|
+
spans,
|
|
20569
20826
|
...deltaCursor !== void 0 ? { deltaCursor } : {}
|
|
20570
20827
|
};
|
|
20571
20828
|
}
|
|
20572
|
-
async function listTracesDelta(client, span, filters, after, limit) {
|
|
20829
|
+
async function listTracesDelta(client, span, projection, filters, after, limit) {
|
|
20573
20830
|
if (after === void 0) {
|
|
20574
20831
|
const deltaCursor = await readTracesStreamHeadCursor(client, span, filters);
|
|
20575
20832
|
return {
|
|
@@ -20589,7 +20846,7 @@ async function listTracesDelta(client, span, filters, after, limit) {
|
|
|
20589
20846
|
params.push(afterCursor.xactId, afterCursor.cursorId);
|
|
20590
20847
|
conditions.push(`r."xactId" < $${i++}::xid8`);
|
|
20591
20848
|
params.push(safeHorizon);
|
|
20592
|
-
const rows = await client.manyOrNone(`SELECT ${
|
|
20849
|
+
const rows = await client.manyOrNone(`SELECT ${projection.columns}
|
|
20593
20850
|
FROM ${span} r
|
|
20594
20851
|
WHERE ${conditions.join(" AND ")}
|
|
20595
20852
|
ORDER BY r."xactId" ASC, r."cursorId" ASC
|
|
@@ -20598,7 +20855,7 @@ async function listTracesDelta(client, span, filters, after, limit) {
|
|
|
20598
20855
|
const visible = rows.slice(0, limit);
|
|
20599
20856
|
const deltaCursor = visible.length > 0 ? encodeDeltaCursor(visible[visible.length - 1].xactId, visible[visible.length - 1].cursorId) : encodeDeltaCursor(safeHorizon, 0);
|
|
20600
20857
|
return {
|
|
20601
|
-
spans:
|
|
20858
|
+
spans: await projection.mapRows(client, span, visible),
|
|
20602
20859
|
delta: {
|
|
20603
20860
|
limit,
|
|
20604
20861
|
hasMore
|
|
@@ -21309,6 +21566,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
21309
21566
|
async listTraces(args) {
|
|
21310
21567
|
return this.#run("LIST_TRACES", () => listTraces(this.#readClient, this.#schema, args));
|
|
21311
21568
|
}
|
|
21569
|
+
async listTracesLight(args) {
|
|
21570
|
+
return this.#run("LIST_TRACES_LIGHT", () => listTracesLight(this.#readClient, this.#schema, args));
|
|
21571
|
+
}
|
|
21312
21572
|
async querySpans(plan) {
|
|
21313
21573
|
return this.#run("QUERY_SPANS", () => querySpans(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
21314
21574
|
}
|