@mastra/duckdb 1.13.0-alpha.0 → 1.13.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/docs/SKILL.md +1 -1
  2. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  3. package/dist/{db-zp2htaur.cjs → features-BzhPmFg2.cjs} +63 -2
  4. package/dist/features-BzhPmFg2.cjs.map +1 -0
  5. package/dist/{db-BWgrbB_u.js → features-D3sUOsCL.js} +23 -3
  6. package/dist/features-D3sUOsCL.js.map +1 -0
  7. package/dist/index.cjs +11 -6
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.js +7 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/{observability-BCEHa8GU.cjs → observability-D4eFjsKJ.cjs} +279 -77
  12. package/dist/observability-D4eFjsKJ.cjs.map +1 -0
  13. package/dist/{observability-TpgAucZM.js → observability-DINJlR_a.js} +276 -52
  14. package/dist/observability-DINJlR_a.js.map +1 -0
  15. package/dist/storage/db/index.d.ts +5 -1
  16. package/dist/storage/db/index.d.ts.map +1 -1
  17. package/dist/storage/domains/observability/helpers.d.ts +17 -0
  18. package/dist/storage/domains/observability/helpers.d.ts.map +1 -1
  19. package/dist/storage/domains/observability/index.d.ts +3 -2
  20. package/dist/storage/domains/observability/index.d.ts.map +1 -1
  21. package/dist/storage/domains/observability/span-query.d.ts +43 -0
  22. package/dist/storage/domains/observability/span-query.d.ts.map +1 -0
  23. package/dist/storage/domains/observability/trace-query.d.ts +8 -1
  24. package/dist/storage/domains/observability/trace-query.d.ts.map +1 -1
  25. package/dist/storage/domains/observability/tracing.d.ts.map +1 -1
  26. package/dist/storage/features.d.ts +2 -0
  27. package/dist/storage/features.d.ts.map +1 -0
  28. package/dist/storage/index.d.ts +1 -0
  29. package/dist/storage/index.d.ts.map +1 -1
  30. package/package.json +3 -3
  31. package/dist/db-BWgrbB_u.js.map +0 -1
  32. package/dist/db-zp2htaur.cjs.map +0 -1
  33. package/dist/observability-BCEHa8GU.cjs.map +0 -1
  34. package/dist/observability-TpgAucZM.js.map +0 -1
@@ -1,29 +1,7 @@
1
- //#region \0rolldown/runtime.js
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
- //#endregion
23
- const require_db = require("./db-zp2htaur.cjs");
1
+ const require_features = require("./features-BzhPmFg2.cjs");
24
2
  let _mastra_core_error = require("@mastra/core/error");
25
3
  let _mastra_core_storage = require("@mastra/core/storage");
26
- _mastra_core_storage = __toESM(_mastra_core_storage, 1);
4
+ _mastra_core_storage = require_features.__toESM(_mastra_core_storage, 1);
27
5
  let _mastra_core_features = require("@mastra/core/features");
28
6
  let _mastra_core_observability = require("@mastra/core/observability");
29
7
  let _mastra_core_utils = require("@mastra/core/utils");
@@ -703,11 +681,38 @@ function buildPaginationClause(pagination) {
703
681
  //#endregion
704
682
  //#region src/storage/domains/observability/helpers.ts
705
683
  /** Shorthand for {@link DuckDBConnection.sqlValue}. */
706
- const v = require_db.DuckDBConnection.sqlValue;
684
+ const v = require_features.DuckDBConnection.sqlValue;
707
685
  /** Serialize a value to JSON then SQL-escape it, or return 'NULL'. */
708
686
  function jsonV(val) {
709
687
  if (val === null || val === void 0) return "NULL";
710
- return require_db.DuckDBConnection.sqlValue(JSON.stringify(val));
688
+ return require_features.DuckDBConnection.sqlValue(JSON.stringify(val));
689
+ }
690
+ /**
691
+ * `span_events` JSON columns that can hold large payloads. Missing values are
692
+ * stored as the JSON literal `null` instead of SQL NULL.
693
+ *
694
+ * DuckDB only reads the selected rows of a large-string column when the
695
+ * column's validity mask is constant (no NULLs in the row group). Any SQL NULL
696
+ * makes it fall back to reading the whole 2,048-row vector, which loads every
697
+ * large string in it even when the query needs one row. Keeping these columns
698
+ * NULL-free lets point lookups and page reads load only what they return.
699
+ */
700
+ const SPAN_PAYLOAD_COLUMNS = [
701
+ "attributes",
702
+ "input",
703
+ "output",
704
+ "requestContext"
705
+ ];
706
+ /** Stored in {@link SPAN_PAYLOAD_COLUMNS} in place of SQL NULL; read back as missing. */
707
+ const JSON_NULL_PLACEHOLDER = "null";
708
+ /** Like {@link jsonV}, but writes the JSON `null` placeholder instead of SQL NULL. */
709
+ function payloadJsonV(val) {
710
+ if (val === null || val === void 0) return `'${JSON_NULL_PLACEHOLDER}'`;
711
+ return jsonV(val);
712
+ }
713
+ /** SQL for a payload column that maps the JSON `null` placeholder back to SQL NULL. */
714
+ function payloadColumnSql(column) {
715
+ return `NULLIF(${column}, '${JSON_NULL_PLACEHOLDER}')`;
711
716
  }
712
717
  /**
713
718
  * Trim, drop blank, and dedupe tags before they are written, matching the
@@ -2957,7 +2962,7 @@ const TRACE_SELECT = `
2957
2962
  r.entityId AS entityId,
2958
2963
  r.parentSpanId AS parentSpanId,
2959
2964
  r.metadata AS metadata,
2960
- r.input AS input,
2965
+ ${payloadColumnSql("r.input")} AS input,
2961
2966
  r.threadId AS threadId,
2962
2967
  r.resourceId AS resourceId,
2963
2968
  r.startedAt AS startedAt,
@@ -3240,6 +3245,11 @@ function compileDuckDBTraceScope(relatedCollections, scope, timeRange, rootPredi
3240
3245
  GROUP BY r.traceId
3241
3246
  HAVING bool_or(r.timestamp >= CAST(? AS TIMESTAMP) AND r.timestamp < CAST(? AS TIMESTAMP))
3242
3247
  AND bool_or(${rowConditions.join("\n AND ")})
3248
+ )`,
3249
+ `root_bounds AS (
3250
+ SELECT min(timestamp) AS minTs, max(timestamp) AS maxTs
3251
+ FROM span_events
3252
+ WHERE parentSpanId IS NULL AND traceId IN (SELECT traceId FROM root_trace_ids)
3243
3253
  )`,
3244
3254
  `root_events AS (
3245
3255
  SELECT
@@ -3250,6 +3260,8 @@ function compileDuckDBTraceScope(relatedCollections, scope, timeRange, rootPredi
3250
3260
  END AS startedAt
3251
3261
  FROM span_events
3252
3262
  WHERE parentSpanId IS NULL
3263
+ AND timestamp >= (SELECT minTs FROM root_bounds)
3264
+ AND timestamp <= (SELECT maxTs FROM root_bounds)
3253
3265
  AND traceId IN (SELECT traceId FROM root_trace_ids)
3254
3266
  )`,
3255
3267
  `current_roots AS (
@@ -3272,7 +3284,15 @@ function compileDuckDBTraceScope(relatedCollections, scope, timeRange, rootPredi
3272
3284
  )`
3273
3285
  ];
3274
3286
  if (relatedCollections.has("spans")) {
3275
- ctes.push(`current_span_rows AS (
3287
+ ctes.push(`span_bounds AS (
3288
+ -- Exact event-time range of the in-scope traces, from a narrow
3289
+ -- (traceId, timestamp) pass. The join below cannot be pushed into the
3290
+ -- scan, so the range keeps it from decompressing every span row.
3291
+ SELECT min(timestamp) AS minTs, max(timestamp) AS maxTs
3292
+ FROM span_events
3293
+ WHERE traceId IN (SELECT traceId FROM root_scope)
3294
+ ),
3295
+ current_span_rows AS (
3276
3296
  SELECT
3277
3297
  e.*,
3278
3298
  coalesce(
@@ -3284,7 +3304,9 @@ function compileDuckDBTraceScope(relatedCollections, scope, timeRange, rootPredi
3284
3304
  ORDER BY CASE WHEN e.endedAt IS NULL THEN 1 ELSE 0 END ASC, e.cursorId DESC
3285
3305
  ) AS currentRank
3286
3306
  FROM span_events e
3287
- INNER JOIN root_scope roots ON roots.traceId = e.traceId${tenantWhere("e")}
3307
+ INNER JOIN root_scope roots ON roots.traceId = e.traceId
3308
+ WHERE e.timestamp >= (SELECT minTs FROM span_bounds)
3309
+ AND e.timestamp <= (SELECT maxTs FROM span_bounds)${tenantConditions("e").map((condition) => `\n AND ${condition}`).join("")}
3288
3310
  ),
3289
3311
  current_spans AS (
3290
3312
  SELECT
@@ -3688,6 +3710,164 @@ async function queryThreads(db, plan) {
3688
3710
  }) : null }
3689
3711
  });
3690
3712
  }
3713
+ /** Compile a span-row filter with exactly the same rules as trace span predicates. */
3714
+ function compileSpanQueryPredicate(predicate) {
3715
+ return compileScalarPredicate(predicate, SPAN_FIELDS);
3716
+ }
3717
+ //#endregion
3718
+ //#region src/storage/domains/observability/span-query.ts
3719
+ const identityFields = [
3720
+ "organizationId",
3721
+ "resourceId",
3722
+ "traceId",
3723
+ "spanId"
3724
+ ];
3725
+ const identities = identityFields.join(", ");
3726
+ const fields = [
3727
+ ...identityFields,
3728
+ "parentSpanId",
3729
+ "name",
3730
+ "spanType",
3731
+ "entityType",
3732
+ "entityId",
3733
+ "entityName",
3734
+ "entityVersionId",
3735
+ "parentEntityVersionId",
3736
+ "rootEntityVersionId",
3737
+ "runId",
3738
+ "sessionId",
3739
+ "userId"
3740
+ ];
3741
+ function keys(rows, values, alias = "") {
3742
+ return rows.map((row) => `(${identityFields.map((field) => {
3743
+ values.push(row[field]);
3744
+ return `${alias}${field} IS NOT DISTINCT FROM ?`;
3745
+ }).join(" AND ")})`).join(" OR ");
3746
+ }
3747
+ function afterCursor(plan) {
3748
+ if (!plan.cursor) return {
3749
+ sql: "TRUE",
3750
+ values: []
3751
+ };
3752
+ const cursor = plan.cursor;
3753
+ const comparisons = [`s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? ">" : "<"} CAST(? AS TIMESTAMP)`];
3754
+ const values = [cursor.sortValue];
3755
+ for (let i = 0; i < identityFields.length; i++) {
3756
+ const field = identityFields[i];
3757
+ if (cursor[field] === null) continue;
3758
+ const equal = [`s.${plan.orderBy.field} = CAST(? AS TIMESTAMP)`];
3759
+ values.push(cursor.sortValue);
3760
+ for (const prior of identityFields.slice(0, i)) {
3761
+ equal.push(`s.${prior} IS NOT DISTINCT FROM ?`);
3762
+ values.push(cursor[prior]);
3763
+ }
3764
+ values.push(cursor[field]);
3765
+ comparisons.push(`(${equal.join(" AND ")} AND (s.${field} > ? OR s.${field} IS NULL))`);
3766
+ }
3767
+ return {
3768
+ sql: `(${comparisons.join(" OR ")})`,
3769
+ values
3770
+ };
3771
+ }
3772
+ function compileDuckDBSpanQuery(plan) {
3773
+ const values = [plan.timeRange.from, plan.timeRange.to];
3774
+ const scope = [];
3775
+ if (plan.scope) {
3776
+ scope.push("organizationId = ?");
3777
+ values.push(plan.scope.organizationId);
3778
+ if (plan.scope.resourceId !== void 0) {
3779
+ scope.push("resourceId = ?");
3780
+ values.push(plan.scope.resourceId);
3781
+ }
3782
+ }
3783
+ const predicate = plan.where ? compileSpanQueryPredicate(plan.where) : {
3784
+ sql: "TRUE",
3785
+ values: []
3786
+ };
3787
+ const after = afterCursor(plan);
3788
+ values.push(plan.timeRange.from, plan.timeRange.to, ...predicate.values, ...after.values, plan.limit + 1);
3789
+ const join = identityFields.map((field) => `e.${field} IS NOT DISTINCT FROM c.${field}`).join(" AND ");
3790
+ return {
3791
+ sql: `WITH candidates AS MATERIALIZED (
3792
+ SELECT ${identities} FROM span_events
3793
+ WHERE eventType = 'start' AND timestamp >= CAST(? AS TIMESTAMP) AND timestamp < CAST(? AS TIMESTAMP)
3794
+ ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
3795
+ GROUP BY ${identities}
3796
+ ), ranked AS (
3797
+ SELECT ${fields.map((field) => `e.${field}`).join(", ")},
3798
+ min(e.timestamp) FILTER (WHERE e.eventType = 'start') OVER (PARTITION BY ${identityFields.map((field) => `e.${field}`).join(", ")}) AS startedAt, e.endedAt, e.cursorId,
3799
+ CASE WHEN json_type(e.attributes, '$.model') = 'VARCHAR' THEN json_extract_string(e.attributes, '$.model') END AS model,
3800
+ CASE WHEN json_type(e.attributes, '$.provider') = 'VARCHAR' THEN json_extract_string(e.attributes, '$.provider') END AS provider,
3801
+ date_diff('millisecond', min(e.timestamp) FILTER (WHERE e.eventType = 'start') OVER (PARTITION BY ${identityFields.map((field) => `e.${field}`).join(", ")}), e.endedAt) AS durationMs,
3802
+ CASE WHEN e.error IS NULL THEN 'success' ELSE 'error' END AS status,
3803
+ CASE WHEN e.error IS NULL THEN NULL ELSE 'error' END AS error,
3804
+ row_number() OVER (PARTITION BY ${identityFields.map((field) => `e.${field}`).join(", ")}
3805
+ ORDER BY CASE WHEN e.endedAt IS NULL THEN 1 ELSE 0 END, e.cursorId DESC NULLS LAST, e.endedAt DESC) AS rank
3806
+ FROM span_events e INNER JOIN candidates c ON ${join}
3807
+ )
3808
+ SELECT ${[
3809
+ ...identityFields,
3810
+ "parentSpanId",
3811
+ "name",
3812
+ "spanType",
3813
+ "entityType",
3814
+ "entityId",
3815
+ "entityName",
3816
+ "model",
3817
+ "provider",
3818
+ "startedAt",
3819
+ "endedAt",
3820
+ "durationMs",
3821
+ "status",
3822
+ "cursorId"
3823
+ ].map((field) => `s.${field}`).join(", ")}
3824
+ FROM ranked s WHERE rank = 1 AND endedAt IS NOT NULL AND startedAt >= CAST(? AS TIMESTAMP) AND startedAt < CAST(? AS TIMESTAMP) AND (${predicate.sql}) AND ${after.sql}
3825
+ ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"}, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
3826
+ LIMIT ?`,
3827
+ values
3828
+ };
3829
+ }
3830
+ async function querySpans(db, plan) {
3831
+ const deadline = performance.now() + _mastra_core_storage.TRACE_QUERY_DEFAULT_TIMEOUT_MS;
3832
+ const query = async (sql, values) => {
3833
+ const remaining = Math.floor(deadline - performance.now());
3834
+ if (remaining <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
3835
+ try {
3836
+ return await db.query(sql, values, remaining);
3837
+ } catch (error) {
3838
+ if (error instanceof require_features.DuckDBQueryTimeoutError) throw new _mastra_core_storage.TraceQueryExecutionError();
3839
+ if (error instanceof Error && /out of memory/i.test(error.message)) throw new _mastra_core_storage.TraceQueryResourceLimitError();
3840
+ throw error;
3841
+ }
3842
+ };
3843
+ const compiled = compileDuckDBSpanQuery(plan);
3844
+ const rows = await query(compiled.sql, compiled.values);
3845
+ const selected = rows.map(({ cursorId: _cursorId, startedAt, endedAt, ...row }) => ({
3846
+ ...row,
3847
+ startedAt: startedAt.toISOString(),
3848
+ endedAt: endedAt.toISOString()
3849
+ }));
3850
+ const page = rows.slice(0, plan.limit);
3851
+ if (!page.length) return _mastra_core_storage.buildSpanQueryResponse(plan, selected, [], []);
3852
+ const payloadValues = [];
3853
+ const payloadWhere = page.map((row) => {
3854
+ const key = keys([row], payloadValues);
3855
+ payloadValues.push(row.cursorId, row.endedAt);
3856
+ return `(${key} AND cursorId IS NOT DISTINCT FROM ? AND endedAt = ?)`;
3857
+ }).join(" OR ");
3858
+ const payloads = await query(`SELECT DISTINCT ${identities},
3859
+ substring(CAST(${payloadColumnSql("input")} AS VARCHAR), 1, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}) AS inputPreview, substring(CAST(${payloadColumnSql("output")} AS VARCHAR), 1, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}) AS outputPreview
3860
+ FROM span_events WHERE ${payloadWhere}`, payloadValues);
3861
+ const metricValues = [];
3862
+ const metricWhere = keys(page, metricValues);
3863
+ const metrics = await query(`SELECT ${identities}, name, estimatedCost, costUnit,
3864
+ json_extract_string(costMetadata, '$.allocation') AS allocation,
3865
+ COALESCE(json_exists(costMetadata, '$.error'), false) AS costError
3866
+ FROM metric_events WHERE (${metricWhere}) AND name LIKE 'mastra_model_%'
3867
+ LIMIT ${_mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS + 1}`, metricValues);
3868
+ if (metrics.length > _mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS) throw new _mastra_core_storage.TraceQueryResourceLimitError();
3869
+ return _mastra_core_storage.buildSpanQueryResponse(plan, selected, payloads, metrics);
3870
+ }
3691
3871
  //#endregion
3692
3872
  //#region src/storage/domains/observability/trace-aggregate.ts
3693
3873
  /** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
@@ -3931,13 +4111,15 @@ const COLUMNS_SQL = [
3931
4111
  "error",
3932
4112
  "requestContext"
3933
4113
  ].join(", ");
4114
+ const PAYLOAD_COLUMN_SET = new Set(SPAN_PAYLOAD_COLUMNS);
3934
4115
  /**
3935
4116
  * Reconstruction query uses `arg_max(field, timestamp) FILTER (WHERE field IS NOT NULL)`
3936
4117
  * so that the final end event supplies the terminal span fields without wiping
3937
- * stable values emitted on the start event.
4118
+ * stable values emitted on the start event. Payload columns also skip the JSON
4119
+ * `null` placeholder, which stands in for a missing value.
3938
4120
  */
3939
4121
  function argMaxNonNull(col) {
3940
- return `arg_max(${col}, timestamp) FILTER (WHERE ${col} IS NOT NULL) as ${col}`;
4122
+ return `arg_max(${col}, timestamp) FILTER (WHERE ${col} IS NOT NULL${PAYLOAD_COLUMN_SET.has(col) ? ` AND ${col} <> '${JSON_NULL_PLACEHOLDER}'` : ""}) as ${col}`;
3941
4123
  }
3942
4124
  const SPAN_RECONSTRUCT_SELECT = `
3943
4125
  SELECT
@@ -4048,28 +4230,55 @@ function buildPostAggReconstructSelect(postAgg, orderByField) {
4048
4230
  `;
4049
4231
  }
4050
4232
  /**
4051
- * Reconstruct spans for the `(traceId, spanId)` pairs selected by `anchorCte`,
4052
- * scanning only events at/after the CTE's earliest `anchorStartedAt`.
4233
+ * Reconstruct the spans of one list page, reading payload columns only for
4234
+ * the page's own events.
4053
4235
  *
4054
- * The `(traceId, spanId) IN (subquery)` semi-join alone cannot be pushed into
4055
- * the table scan, so without a bound DuckDB decompresses every column of the
4056
- * entire table just to emit one page of spans. The time bound is a plain range
4057
- * predicate that zone maps can prune on (insertion order tracks event time).
4236
+ * Two queries:
4237
+ * 1. `anchorSql` (a `WITH ...` prefix defining `anchorCte`) picks the page's
4238
+ * `(traceId, spanId)` anchors, and a narrow pass over `traceId`, `spanId`
4239
+ * and `timestamp` finds the exact time range of their events.
4240
+ * 2. Reconstruction scans only that range for exactly those anchors.
4058
4241
  *
4059
- * Correctness: span events are only 'start' (timestamp = startedAt) and 'end'
4060
- * (timestamp = endedAt >= startedAt), so every event of an anchored span has
4061
- * timestamp >= its start-row timestamp >= min(anchorStartedAt). An empty
4062
- * anchor set makes the bound NULL, which matches the empty IN-list result.
4242
+ * The `(traceId, spanId) IN (...)` semi-join cannot be pushed into the table
4243
+ * scan, so without a range DuckDB decompresses every payload column of every
4244
+ * row it reads. A lower bound alone (the earliest anchor) prunes nothing for
4245
+ * the oldest pages, which then read the whole table and can run out of memory.
4246
+ * Both bounds are passed as literal parameters so they always reach the scan
4247
+ * as plain range filters that zone maps can prune on.
4248
+ *
4249
+ * Anchors and bounds are frozen after the first query, so spans written in
4250
+ * between cannot change which rows are on the page. An event a page span
4251
+ * receives in between is left for the next read, as if the page had been
4252
+ * read a moment earlier.
4063
4253
  */
4064
- function reconstructForAnchors(reconstructSelect, anchorCte) {
4065
- return `
4066
- ${reconstructSelect}
4067
- WHERE timestamp >= (SELECT min(anchorStartedAt) FROM ${anchorCte})
4068
- AND (traceId, spanId) IN (SELECT traceId, spanId FROM ${anchorCte})
4069
- GROUP BY traceId, spanId`;
4254
+ async function reconstructAnchoredPage(db, anchorSql, anchorCte, params, reconstructSelect, orderBySql) {
4255
+ const anchors = await db.query(`
4256
+ ${anchorSql}
4257
+ SELECT anchors.traceId, anchors.spanId, bounds.minTs, bounds.maxTs
4258
+ FROM ${anchorCte} AS anchors,
4259
+ (
4260
+ SELECT min(timestamp) AS minTs, max(timestamp) AS maxTs
4261
+ FROM span_events
4262
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM ${anchorCte})
4263
+ ) AS bounds
4264
+ `, params);
4265
+ if (anchors.length === 0) return [];
4266
+ const { minTs, maxTs } = anchors[0];
4267
+ const anchorValues = anchors.map(() => "(?, ?)").join(", ");
4268
+ return db.query(`
4269
+ ${reconstructSelect}
4270
+ WHERE timestamp >= ? AND timestamp <= ?
4271
+ AND (traceId, spanId) IN (SELECT * FROM (VALUES ${anchorValues}))
4272
+ GROUP BY traceId, spanId
4273
+ ${orderBySql}
4274
+ `, [
4275
+ minTs,
4276
+ maxTs,
4277
+ ...anchors.flatMap((anchor) => [anchor.traceId, anchor.spanId])
4278
+ ]);
4070
4279
  }
4071
4280
  /**
4072
- * Same time-bound trick as {@link reconstructForAnchors}, but with the bound
4281
+ * Same time-bound trick as {@link reconstructAnchoredPage}, but with the bound
4073
4282
  * precomputed in JS and passed as a `?` parameter. The delta-poll query shape
4074
4283
  * references its candidate CTE multiple times, which makes DuckDB materialize
4075
4284
  * it and lose the dynamic-filter pushdown a scalar subquery bound relies on —
@@ -4303,15 +4512,15 @@ function toValuesTuple(row) {
4303
4512
  v(row.environment),
4304
4513
  v(row.source),
4305
4514
  v(row.serviceName),
4306
- jsonV(row.attributes),
4515
+ payloadJsonV(row.attributes),
4307
4516
  jsonV(row.metadata),
4308
4517
  jsonV(row.tags),
4309
4518
  jsonV(row.scope),
4310
4519
  jsonV(row.links),
4311
- jsonV(row.input),
4312
- jsonV(row.output),
4520
+ payloadJsonV(row.input),
4521
+ payloadJsonV(row.output),
4313
4522
  jsonV(row.error),
4314
- jsonV(row.requestContext)
4523
+ payloadJsonV(row.requestContext)
4315
4524
  ].join(", ");
4316
4525
  }
4317
4526
  async function insertSpanEvents(db, rows) {
@@ -4525,7 +4734,7 @@ async function listTraceRows(db, args, reconstructSelect, mapRow, toSpans) {
4525
4734
  },
4526
4735
  spans: []
4527
4736
  };
4528
- const pageSql = `
4737
+ const spans = (await reconstructAnchoredPage(db, `
4529
4738
  WITH page_roots AS (
4530
4739
  SELECT traceId, spanId, min(timestamp) AS anchorStartedAt
4531
4740
  FROM span_events AS ${outerAlias}
@@ -4534,14 +4743,11 @@ async function listTraceRows(db, args, reconstructSelect, mapRow, toSpans) {
4534
4743
  ${prefilterOrderBy}
4535
4744
  LIMIT ? OFFSET ?
4536
4745
  )
4537
- ${reconstructForAnchors(reconstructSelect, "page_roots")}
4538
- ${buildOrderByClause(orderBy)}, traceId, spanId
4539
- `;
4540
- const spans = (await db.query(pageSql, [
4746
+ `, "page_roots", [
4541
4747
  ...prefilterParams,
4542
4748
  perPage,
4543
4749
  offset
4544
- ])).map((row) => mapRow(row));
4750
+ ], reconstructSelect, `${buildOrderByClause(orderBy)}, traceId, spanId`)).map((row) => mapRow(row));
4545
4751
  return {
4546
4752
  pagination: {
4547
4753
  total,
@@ -4590,20 +4796,17 @@ async function listTraceRows(db, args, reconstructSelect, mapRow, toSpans) {
4590
4796
  },
4591
4797
  spans: []
4592
4798
  };
4593
- const dataSql = `
4799
+ const spans = (await reconstructAnchoredPage(db, `
4594
4800
  ${cteSql},
4595
4801
  page_roots AS (
4596
4802
  SELECT traceId, spanId, startedAt AS anchorStartedAt
4597
4803
  FROM root_spans ${postAggWhere} ${orderByClause} ${paginationClause}
4598
4804
  )
4599
- ${reconstructForAnchors(reconstructSelect, "page_roots")}
4600
- ${orderByClause}
4601
- `;
4602
- const spans = (await db.query(dataSql, [
4805
+ `, "page_roots", [
4603
4806
  ...prefilterParams,
4604
4807
  ...postAggParams,
4605
4808
  ...paginationParams
4606
- ])).map((row) => mapRow(row));
4809
+ ], reconstructSelect, orderByClause)).map((row) => mapRow(row));
4607
4810
  return {
4608
4811
  pagination: {
4609
4812
  total,
@@ -4907,7 +5110,7 @@ async function listBranches(db, args) {
4907
5110
  branches: [],
4908
5111
  ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
4909
5112
  };
4910
- const pageSql = `
5113
+ const spans = (await reconstructAnchoredPage(db, `
4911
5114
  WITH page_anchors AS (
4912
5115
  SELECT traceId, spanId, min(timestamp) AS anchorStartedAt
4913
5116
  FROM span_events AS ${outerAlias}
@@ -4916,14 +5119,11 @@ async function listBranches(db, args) {
4916
5119
  ${prefilterOrderBy}
4917
5120
  LIMIT ? OFFSET ?
4918
5121
  )
4919
- ${reconstructForAnchors(SPAN_RECONSTRUCT_SELECT, "page_anchors")}
4920
- ${buildOrderByClause(orderBy)}, traceId, spanId
4921
- `;
4922
- const spans = (await db.query(pageSql, [
5122
+ `, "page_anchors", [
4923
5123
  ...prefilterParams,
4924
5124
  perPage,
4925
5125
  offset
4926
- ])).map((row) => rowToSpanRecord(row));
5126
+ ], SPAN_RECONSTRUCT_SELECT, `${buildOrderByClause(orderBy)}, traceId, spanId`)).map((row) => rowToSpanRecord(row));
4927
5127
  return {
4928
5128
  pagination: {
4929
5129
  total,
@@ -4970,20 +5170,17 @@ async function listBranches(db, args) {
4970
5170
  branches: [],
4971
5171
  ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
4972
5172
  };
4973
- const dataSql = `
5173
+ const spans = (await reconstructAnchoredPage(db, `
4974
5174
  ${cteSql},
4975
5175
  page_anchors AS (
4976
5176
  SELECT traceId, spanId, startedAt AS anchorStartedAt
4977
5177
  FROM branch_anchors ${postAggWhere} ${orderByClause} ${paginationClause}
4978
5178
  )
4979
- ${reconstructForAnchors(SPAN_RECONSTRUCT_SELECT, "page_anchors")}
4980
- ${orderByClause}
4981
- `;
4982
- const spans = (await db.query(dataSql, [
5179
+ `, "page_anchors", [
4983
5180
  ...prefilterParams,
4984
5181
  ...postAggParams,
4985
5182
  ...paginationParams
4986
- ])).map((row) => rowToSpanRecord(row));
5183
+ ], SPAN_RECONSTRUCT_SELECT, orderByClause)).map((row) => rowToSpanRecord(row));
4987
5184
  return {
4988
5185
  pagination: {
4989
5186
  total,
@@ -5220,6 +5417,7 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends _mastr
5220
5417
  "metric-discovery",
5221
5418
  "trace-query",
5222
5419
  "trace-aggregate",
5420
+ ...require_features.spanQueryFeatures,
5223
5421
  "trace-query-root-duration",
5224
5422
  "trace-query-discovery",
5225
5423
  "thread-query",
@@ -5239,6 +5437,7 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends _mastr
5239
5437
  "delta-polling",
5240
5438
  "trace-query",
5241
5439
  "trace-aggregate",
5440
+ ...require_features.spanQueryFeatures,
5242
5441
  "trace-query-root-duration",
5243
5442
  "trace-query-discovery",
5244
5443
  "thread-query",
@@ -5274,6 +5473,9 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends _mastr
5274
5473
  async listTraces(args) {
5275
5474
  return listTraces(this.db, args);
5276
5475
  }
5476
+ async querySpans(plan) {
5477
+ return querySpans(this.db, plan);
5478
+ }
5277
5479
  async queryTraces(plan) {
5278
5480
  return queryTraces(this.db, plan);
5279
5481
  }
@@ -5402,4 +5604,4 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends _mastr
5402
5604
  //#endregion
5403
5605
  exports.ObservabilityStorageDuckDB = ObservabilityStorageDuckDB;
5404
5606
 
5405
- //# sourceMappingURL=observability-BCEHa8GU.cjs.map
5607
+ //# sourceMappingURL=observability-D4eFjsKJ.cjs.map