@mastra/pg 1.25.0 → 1.26.0-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/index.js CHANGED
@@ -2460,6 +2460,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2460
2460
  }
2461
2461
  //#endregion
2462
2462
  //#region src/storage/db/index.ts
2463
+ const POSTGRES_MAX_BIND_PARAMETERS$1 = 65535;
2463
2464
  /**
2464
2465
  * Resolves PgDomainConfig to a database client and schema.
2465
2466
  * Handles creating a new pool if config is provided.
@@ -3013,6 +3014,90 @@ var PgDB = class extends MastraBase {
3013
3014
  ON CONFLICT ("traceId", "spanId") DO NOTHING`, values);
3014
3015
  } else await client.none(`INSERT INTO ${fullTableName} (${columnList}) VALUES (${placeholders})`, values);
3015
3016
  }
3017
+ getChunkRowLimit(columnCount) {
3018
+ if (columnCount === 0) return 0;
3019
+ return Math.max(1, Math.floor(POSTGRES_MAX_BIND_PARAMETERS$1 / columnCount));
3020
+ }
3021
+ getSpanConflictIdentifier(record) {
3022
+ const traceId = record.traceId;
3023
+ const spanId = record.spanId;
3024
+ if (traceId === void 0 || spanId === void 0) return;
3025
+ return `${String(traceId)}|${String(spanId)}`;
3026
+ }
3027
+ async normalizeForInsert(tableName, record) {
3028
+ this.addTimestampZColumns(record);
3029
+ const filteredRecord = await this.filterRecordToKnownColumns(tableName, record);
3030
+ return {
3031
+ columns: Object.keys(filteredRecord).map((column) => parseSqlIdentifier(column, "column name")),
3032
+ values: this.prepareValuesForInsert(filteredRecord, tableName),
3033
+ conflictKey: tableName === TABLE_SPANS ? this.getSpanConflictIdentifier(filteredRecord) : void 0
3034
+ };
3035
+ }
3036
+ buildMultiRowInsertStatement({ tableName, columns, rows }) {
3037
+ const fullTableName = getTableName$6({
3038
+ indexName: tableName,
3039
+ schemaName: getSchemaName$6(this.schemaName)
3040
+ });
3041
+ const columnList = columns.map((column) => `"${column}"`).join(", ");
3042
+ const bindParams = [];
3043
+ const values = [];
3044
+ let bindIndex = 1;
3045
+ for (const rowValues of rows) {
3046
+ const placeholders = rowValues.map(() => `$${bindIndex++}`);
3047
+ bindParams.push(`(${placeholders.join(", ")})`);
3048
+ values.push(...rowValues);
3049
+ }
3050
+ let query = `INSERT INTO ${fullTableName} (${columnList}) VALUES ${bindParams.join(", ")}`;
3051
+ if (tableName === TABLE_SPANS) {
3052
+ const updateColumns = columns.filter((column) => column !== "traceId" && column !== "spanId");
3053
+ if (updateColumns.length > 0) {
3054
+ const updateClause = updateColumns.map((column) => `"${column}" = EXCLUDED."${column}"`).join(", ");
3055
+ query += ` ON CONFLICT ("traceId", "spanId") DO UPDATE SET ${updateClause}`;
3056
+ } else query += ` ON CONFLICT ("traceId", "spanId") DO NOTHING`;
3057
+ }
3058
+ return {
3059
+ query,
3060
+ values
3061
+ };
3062
+ }
3063
+ async executeBatchInsert(client, { tableName, records }) {
3064
+ const preparedRecords = [];
3065
+ for (const record of records) preparedRecords.push(await this.normalizeForInsert(tableName, record));
3066
+ let pendingColumns;
3067
+ let pendingConflictKeys = /* @__PURE__ */ new Set();
3068
+ let pendingRows = [];
3069
+ let pendingLimit = 0;
3070
+ const flush = async () => {
3071
+ if (!pendingColumns || pendingRows.length === 0) return;
3072
+ const statement = this.buildMultiRowInsertStatement({
3073
+ tableName,
3074
+ columns: pendingColumns,
3075
+ rows: pendingRows
3076
+ });
3077
+ await client.none(statement.query, statement.values);
3078
+ pendingColumns = void 0;
3079
+ pendingRows = [];
3080
+ pendingConflictKeys = /* @__PURE__ */ new Set();
3081
+ pendingLimit = 0;
3082
+ };
3083
+ for (const { columns, values, conflictKey } of preparedRecords) {
3084
+ if (columns.length === 0) continue;
3085
+ const columnsSignature = columns.join("\0");
3086
+ const currentPendingColumns = pendingColumns;
3087
+ const isSpans = tableName === TABLE_SPANS;
3088
+ const conflictDuplicate = isSpans && conflictKey !== void 0 && pendingConflictKeys.has(conflictKey);
3089
+ const exceedsLimit = pendingRows.length >= pendingLimit;
3090
+ if (currentPendingColumns === void 0 || columnsSignature !== currentPendingColumns.join("\0") || conflictDuplicate || exceedsLimit) {
3091
+ await flush();
3092
+ pendingColumns = columns;
3093
+ pendingLimit = this.getChunkRowLimit(columns.length);
3094
+ pendingRows = [values];
3095
+ pendingConflictKeys = /* @__PURE__ */ new Set();
3096
+ } else pendingRows.push(values);
3097
+ if (isSpans && conflictKey !== void 0) pendingConflictKeys.add(conflictKey);
3098
+ }
3099
+ await flush();
3100
+ }
3016
3101
  async insert({ tableName, record }) {
3017
3102
  try {
3018
3103
  await this.executeInsert(this.client, {
@@ -3461,9 +3546,9 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3461
3546
  async batchInsert({ tableName, records }) {
3462
3547
  try {
3463
3548
  await this.client.tx(async (tx) => {
3464
- for (const record of records) await this.executeInsert(tx, {
3549
+ await this.executeBatchInsert(tx, {
3465
3550
  tableName,
3466
- record
3551
+ records
3467
3552
  });
3468
3553
  });
3469
3554
  } catch (error) {
@@ -17607,6 +17692,75 @@ LIMIT $${values.length}`,
17607
17692
  values
17608
17693
  };
17609
17694
  }
17695
+ function discoveryRegistry(scope) {
17696
+ if (scope === "trace") return TRACE_FIELDS;
17697
+ if (scope === "spans") return SPAN_FIELDS;
17698
+ if (scope === "scores") return SCORE_FIELDS;
17699
+ return FEEDBACK_FIELDS;
17700
+ }
17701
+ function discoverySource(scope) {
17702
+ if (scope === "trace") return "root_scope r";
17703
+ if (scope === "spans") return "current_spans s";
17704
+ if (scope === "scores") return "current_scores s";
17705
+ return "current_feedback s";
17706
+ }
17707
+ function discoveryCollections(scope) {
17708
+ return scope === "trace" ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([scope]);
17709
+ }
17710
+ function compilePostgresTraceQueryObservedFields(schema, plan) {
17711
+ const { ctes, values } = compilePostgresTraceScope(schema, plan, /* @__PURE__ */ new Set());
17712
+ const searchParameter = values.length + 1;
17713
+ const search = plan.search ? `AND strpos(lower('metadata.' || entry.key), lower($${searchParameter})) > 0` : "";
17714
+ if (plan.search) values.push(plan.search);
17715
+ values.push(plan.limit + 1);
17716
+ return {
17717
+ text: `WITH ${ctes.join(",\n")}
17718
+ SELECT 'metadata.' || entry.key AS path, count(*)::bigint AS occurrences
17719
+ FROM root_scope r
17720
+ CROSS JOIN LATERAL jsonb_each(CASE WHEN jsonb_typeof(r."metadataRaw") = 'object' THEN r."metadataRaw" ELSE '{}'::jsonb END) entry
17721
+ WHERE jsonb_typeof(entry.value) = 'string'
17722
+ AND btrim(entry.value #>> '{}') <> ''
17723
+ AND entry.key <> ''
17724
+ AND strpos(entry.key, '.') = 0
17725
+ AND octet_length('metadata.' || entry.key) <= ${coreStorage.TRACE_QUERY_MAX_PATH_BYTES}
17726
+ AND octet_length(entry.value #>> '{}') <= ${coreStorage.TRACE_QUERY_MAX_STRING_BYTES}
17727
+ ${search}
17728
+ GROUP BY entry.key
17729
+ ORDER BY occurrences DESC, ('metadata.' || entry.key) COLLATE "C" ASC
17730
+ LIMIT $${values.length}`,
17731
+ values
17732
+ };
17733
+ }
17734
+ function compilePostgresTraceQueryValues(schema, plan) {
17735
+ const { ctes, values } = compilePostgresTraceScope(schema, plan, discoveryCollections(plan.predicateScope));
17736
+ let field;
17737
+ if (plan.predicateScope === "trace" && plan.path.startsWith("metadata.")) {
17738
+ const keyParameter = `$${values.length + 1}`;
17739
+ field = `COALESCE(
17740
+ CASE WHEN jsonb_typeof(r."metadataSearch" -> ${keyParameter}) = 'string' THEN r."metadataSearch" ->> ${keyParameter} END,
17741
+ CASE WHEN jsonb_typeof(r."metadataRaw" -> ${keyParameter}) = 'string' THEN NULLIF(btrim(r."metadataRaw" ->> ${keyParameter}), '') END
17742
+ )`;
17743
+ values.push(plan.path.slice(9));
17744
+ } else field = fieldSql(discoveryRegistry(plan.predicateScope), plan.path);
17745
+ const searchParameter = values.length + 1;
17746
+ const search = plan.search ? `AND strpos(lower(value), lower($${searchParameter})) > 0` : "";
17747
+ if (plan.search) values.push(plan.search);
17748
+ values.push(plan.limit + 1);
17749
+ return {
17750
+ text: `WITH ${ctes.join(",\n")}, extracted AS (
17751
+ SELECT ${field}::text AS value FROM ${discoverySource(plan.predicateScope)}
17752
+ )
17753
+ SELECT value, count(*)::bigint AS count
17754
+ FROM extracted
17755
+ WHERE value IS NOT NULL
17756
+ AND octet_length(value) <= ${coreStorage.TRACE_QUERY_MAX_STRING_BYTES}
17757
+ ${search}
17758
+ GROUP BY value
17759
+ ORDER BY count DESC, value COLLATE "C" ASC
17760
+ LIMIT $${values.length}`,
17761
+ values
17762
+ };
17763
+ }
17610
17764
  function asIsoTimestamp$1(value) {
17611
17765
  if (value === null || value === void 0) throw new Error("Trace query returned a null timestamp");
17612
17766
  return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
@@ -17628,6 +17782,29 @@ async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute) {
17628
17782
  throw error;
17629
17783
  }
17630
17784
  }
17785
+ async function getTraceQueryObservedFields(client, schema, plan, timeoutMs) {
17786
+ if (plan.predicateScope !== "trace") return {
17787
+ observedFields: [],
17788
+ observedFieldsTruncated: false
17789
+ };
17790
+ const query = compilePostgresTraceQueryObservedFields(schema, plan);
17791
+ const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
17792
+ return {
17793
+ observedFields: rows.slice(0, plan.limit).map((row) => coreStorage.createTraceQueryObservedFieldDescriptor(String(row.path), Number(row.occurrences))),
17794
+ observedFieldsTruncated: rows.length > plan.limit
17795
+ };
17796
+ }
17797
+ async function getTraceQueryValues(client, schema, plan, timeoutMs) {
17798
+ const query = compilePostgresTraceQueryValues(schema, plan);
17799
+ const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
17800
+ return coreStorage.getTraceQueryValuesResponseSchema.parse({
17801
+ values: rows.slice(0, plan.limit).map((row) => ({
17802
+ value: String(row.value),
17803
+ count: Number(row.count)
17804
+ })),
17805
+ valuesTruncated: rows.length > plan.limit
17806
+ });
17807
+ }
17631
17808
  async function queryTraces(client, schema, plan, timeoutMs) {
17632
17809
  const query = compilePostgresTraceQuery(schema, plan);
17633
17810
  const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
@@ -18574,6 +18751,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
18574
18751
  "metrics",
18575
18752
  "logs",
18576
18753
  "trace-query",
18754
+ "trace-query-discovery",
18577
18755
  "thread-query"
18578
18756
  ];
18579
18757
  return [
@@ -18581,6 +18759,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
18581
18759
  "logs",
18582
18760
  "delta-polling",
18583
18761
  "trace-query",
18762
+ "trace-query-discovery",
18584
18763
  "thread-query"
18585
18764
  ];
18586
18765
  }
@@ -18627,6 +18806,12 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
18627
18806
  async queryTraces(plan) {
18628
18807
  return this.#run("QUERY_TRACES", () => queryTraces(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
18629
18808
  }
18809
+ async getTraceQueryObservedFields(plan) {
18810
+ return this.#run("GET_TRACE_QUERY_OBSERVED_FIELDS", () => getTraceQueryObservedFields(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
18811
+ }
18812
+ async getTraceQueryValues(plan) {
18813
+ return this.#run("GET_TRACE_QUERY_VALUES", () => getTraceQueryValues(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
18814
+ }
18630
18815
  async queryThreads(plan) {
18631
18816
  return this.#run("QUERY_THREADS", () => queryThreads(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
18632
18817
  }
@@ -23406,7 +23591,12 @@ var PgFactoryStorageOps = class {
23406
23591
  const assignments = columns.map((column, i) => `"${column}" = $${i + 1}`).join(", ");
23407
23592
  const filter = this.#buildWhere(schema, where, columns.length + 1);
23408
23593
  const args = [...columns.map((column) => this.#serialize(this.#column(schema, column), set[column])), ...filter.args];
23409
- return (await queryable.query(`UPDATE "${schema.name}" SET ${assignments} WHERE ${filter.sql}`, args)).rowCount ?? 0;
23594
+ try {
23595
+ return (await queryable.query(`UPDATE "${schema.name}" SET ${assignments} WHERE ${filter.sql}`, args)).rowCount ?? 0;
23596
+ } catch (error) {
23597
+ if (isUniqueViolation(error)) throw new UniqueViolationError(collection, { cause: error });
23598
+ throw error;
23599
+ }
23410
23600
  }
23411
23601
  async deleteMany(collection, where) {
23412
23602
  const schema = this.#schema(collection);