@mastra/mysql 0.8.6-alpha.1 → 0.8.6

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
@@ -1863,17 +1863,17 @@ var ChannelsMySQL = class ChannelsMySQL extends ChannelsStorage {
1863
1863
  //#region src/storage/domains/datasets/index.ts
1864
1864
  function parseJSON$3(value) {
1865
1865
  if (value === null || value === void 0) return void 0;
1866
- if (typeof value === "string") {
1867
- if (!value) return void 0;
1868
- try {
1869
- return JSON.parse(value);
1870
- } catch {
1871
- return value;
1872
- }
1866
+ if (typeof value === "string") try {
1867
+ return JSON.parse(value);
1868
+ } catch {
1869
+ return value;
1873
1870
  }
1874
1871
  if (typeof value === "object") return value;
1875
1872
  return value;
1876
1873
  }
1874
+ function parseOptionalJSON(value, emptyValue) {
1875
+ return value === null || value === void 0 ? emptyValue : parseJSON$3(value);
1876
+ }
1877
1877
  function jsonArg(value) {
1878
1878
  return value === void 0 || value === null ? null : JSON.stringify(value);
1879
1879
  }
@@ -2016,14 +2016,10 @@ var DatasetsMySQL = class DatasetsMySQL extends DatasetsStorage {
2016
2016
  await this.pool.execute(`DELETE FROM ${formatTableName(TABLE_DATASETS)}`);
2017
2017
  }
2018
2018
  async experimentTablesExist() {
2019
- try {
2020
- const [rows] = await this.pool.execute(`SELECT COUNT(*) AS c FROM information_schema.tables
2021
- WHERE table_schema = DATABASE() AND table_name IN (?, ?)`, [TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS]);
2022
- const row = Array.isArray(rows) ? rows[0] : void 0;
2023
- return Number(row?.c ?? 0) === 2;
2024
- } catch {
2025
- return false;
2026
- }
2019
+ const [rows] = await this.pool.execute(`SELECT COUNT(*) AS c FROM information_schema.tables
2020
+ WHERE table_schema = DATABASE() AND table_name IN (?, ?)`, [TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS]);
2021
+ const row = Array.isArray(rows) ? rows[0] : void 0;
2022
+ return Number(row?.c ?? 0) === 2;
2027
2023
  }
2028
2024
  mapDataset(row) {
2029
2025
  return {
@@ -2048,6 +2044,8 @@ var DatasetsMySQL = class DatasetsMySQL extends DatasetsStorage {
2048
2044
  };
2049
2045
  }
2050
2046
  mapItem(row) {
2047
+ const metadata = row.metadata ? parseJSON$3(row.metadata) : void 0;
2048
+ const emptyValue = metadata?.__purged === true ? null : void 0;
2051
2049
  return {
2052
2050
  id: row.id,
2053
2051
  datasetId: row.datasetId,
@@ -2055,20 +2053,22 @@ var DatasetsMySQL = class DatasetsMySQL extends DatasetsStorage {
2055
2053
  externalId: row.externalId ?? null,
2056
2054
  organizationId: row.organizationId ?? null,
2057
2055
  projectId: row.projectId ?? null,
2058
- input: parseJSON$3(row.input),
2059
- groundTruth: row.groundTruth ? parseJSON$3(row.groundTruth) : void 0,
2060
- expectedTrajectory: row.expectedTrajectory ? parseJSON$3(row.expectedTrajectory) : void 0,
2061
- toolMocks: row.toolMocks ? parseJSON$3(row.toolMocks) : void 0,
2062
- unmockedToolPolicy: row.unmockedToolPolicy ?? void 0,
2063
- scorerIds: row.scorerIds ? parseJSON$3(row.scorerIds) : void 0,
2064
- requestContext: row.requestContext ? parseJSON$3(row.requestContext) : void 0,
2065
- metadata: row.metadata ? parseJSON$3(row.metadata) : void 0,
2066
- source: row.source ? parseJSON$3(row.source) : void 0,
2056
+ input: row.input === null ? null : parseJSON$3(row.input),
2057
+ groundTruth: parseOptionalJSON(row.groundTruth, emptyValue),
2058
+ expectedTrajectory: parseOptionalJSON(row.expectedTrajectory, emptyValue),
2059
+ toolMocks: parseOptionalJSON(row.toolMocks, emptyValue),
2060
+ unmockedToolPolicy: row.unmockedToolPolicy ?? emptyValue,
2061
+ scorerIds: parseOptionalJSON(row.scorerIds, emptyValue),
2062
+ requestContext: parseOptionalJSON(row.requestContext, emptyValue),
2063
+ metadata,
2064
+ source: parseOptionalJSON(row.source, emptyValue),
2067
2065
  createdAt: parseDateTime(row.createdAt) ?? /* @__PURE__ */ new Date(),
2068
2066
  updatedAt: parseDateTime(row.updatedAt) ?? /* @__PURE__ */ new Date()
2069
2067
  };
2070
2068
  }
2071
2069
  mapItemFull(row) {
2070
+ const metadata = row.metadata ? parseJSON$3(row.metadata) : void 0;
2071
+ const emptyValue = metadata?.__purged === true ? null : void 0;
2072
2072
  return {
2073
2073
  id: row.id,
2074
2074
  datasetId: row.datasetId,
@@ -2078,15 +2078,15 @@ var DatasetsMySQL = class DatasetsMySQL extends DatasetsStorage {
2078
2078
  projectId: row.projectId ?? null,
2079
2079
  validTo: row.validTo,
2080
2080
  isDeleted: Boolean(row.isDeleted),
2081
- input: parseJSON$3(row.input),
2082
- groundTruth: row.groundTruth ? parseJSON$3(row.groundTruth) : void 0,
2083
- expectedTrajectory: row.expectedTrajectory ? parseJSON$3(row.expectedTrajectory) : void 0,
2084
- toolMocks: row.toolMocks ? parseJSON$3(row.toolMocks) : void 0,
2085
- unmockedToolPolicy: row.unmockedToolPolicy ?? void 0,
2086
- scorerIds: row.scorerIds ? parseJSON$3(row.scorerIds) : void 0,
2087
- requestContext: row.requestContext ? parseJSON$3(row.requestContext) : void 0,
2088
- metadata: row.metadata ? parseJSON$3(row.metadata) : void 0,
2089
- source: row.source ? parseJSON$3(row.source) : void 0,
2081
+ input: row.input === null ? null : parseJSON$3(row.input),
2082
+ groundTruth: parseOptionalJSON(row.groundTruth, emptyValue),
2083
+ expectedTrajectory: parseOptionalJSON(row.expectedTrajectory, emptyValue),
2084
+ toolMocks: parseOptionalJSON(row.toolMocks, emptyValue),
2085
+ unmockedToolPolicy: row.unmockedToolPolicy ?? emptyValue,
2086
+ scorerIds: parseOptionalJSON(row.scorerIds, emptyValue),
2087
+ requestContext: parseOptionalJSON(row.requestContext, emptyValue),
2088
+ metadata,
2089
+ source: parseOptionalJSON(row.source, emptyValue),
2090
2090
  createdAt: parseDateTime(row.createdAt) ?? /* @__PURE__ */ new Date(),
2091
2091
  updatedAt: parseDateTime(row.updatedAt) ?? /* @__PURE__ */ new Date()
2092
2092
  };
@@ -2575,6 +2575,53 @@ var DatasetsMySQL = class DatasetsMySQL extends DatasetsStorage {
2575
2575
  connection.release();
2576
2576
  }
2577
2577
  }
2578
+ async _doPurgeItem({ id, datasetId }) {
2579
+ const experimentTablesExist = await this.experimentTablesExist();
2580
+ const connection = await this.pool.getConnection();
2581
+ try {
2582
+ await connection.beginTransaction();
2583
+ await connection.execute(`SELECT ${quoteIdentifier("id", "column name")} FROM ${formatTableName(TABLE_DATASETS)} WHERE ${quoteIdentifier("id", "column name")} = ? FOR UPDATE`, [datasetId]);
2584
+ const itemsTable = formatTableName(TABLE_DATASET_ITEMS);
2585
+ const [rows] = await connection.execute(`SELECT ${quoteIdentifier("id", "column name")} FROM ${itemsTable} WHERE ${quoteIdentifier("id", "column name")} = ? AND ${quoteIdentifier("datasetId", "column name")} = ? LIMIT 1 FOR UPDATE`, [id, datasetId]);
2586
+ if (rows.length === 0) {
2587
+ await connection.commit();
2588
+ return;
2589
+ }
2590
+ const purgedAt = (/* @__PURE__ */ new Date()).toISOString();
2591
+ const purgedMetadata = JSON.stringify({
2592
+ __purged: true,
2593
+ purgedAt
2594
+ });
2595
+ await connection.execute(`UPDATE ${itemsTable} SET ${quoteIdentifier("input", "column name")} = ?, ${quoteIdentifier("groundTruth", "column name")} = NULL, ${quoteIdentifier("expectedTrajectory", "column name")} = NULL, ${quoteIdentifier("toolMocks", "column name")} = NULL, ${quoteIdentifier("unmockedToolPolicy", "column name")} = NULL, ${quoteIdentifier("scorerIds", "column name")} = NULL, ${quoteIdentifier("requestContext", "column name")} = NULL, ${quoteIdentifier("metadata", "column name")} = ?, ${quoteIdentifier("source", "column name")} = NULL WHERE ${quoteIdentifier("id", "column name")} = ? AND ${quoteIdentifier("datasetId", "column name")} = ?`, [
2596
+ "null",
2597
+ purgedMetadata,
2598
+ id,
2599
+ datasetId
2600
+ ]);
2601
+ if (experimentTablesExist) await connection.execute(`UPDATE ${formatTableName(TABLE_EXPERIMENT_RESULTS)} SET ${quoteIdentifier("input", "column name")} = ?, ${quoteIdentifier("output", "column name")} = NULL, ${quoteIdentifier("groundTruth", "column name")} = NULL, ${quoteIdentifier("error", "column name")} = NULL, ${quoteIdentifier("toolMockReport", "column name")} = NULL, ${quoteIdentifier("tags", "column name")} = NULL, ${quoteIdentifier("comment", "column name")} = NULL, ${quoteIdentifier("metadata", "column name")} = ? WHERE ${quoteIdentifier("itemId", "column name")} = ? AND ${quoteIdentifier("experimentId", "column name")} IN (SELECT id FROM ${formatTableName(TABLE_EXPERIMENTS)} WHERE ${quoteIdentifier("datasetId", "column name")} = ?)`, [
2602
+ "null",
2603
+ purgedMetadata,
2604
+ id,
2605
+ datasetId
2606
+ ]);
2607
+ await connection.commit();
2608
+ } catch (error) {
2609
+ let transactionError = error;
2610
+ try {
2611
+ await connection.rollback();
2612
+ } catch (rollbackError) {
2613
+ transactionError = new AggregateError([error, rollbackError], "Transaction and rollback both failed");
2614
+ }
2615
+ if (transactionError instanceof MastraError) throw transactionError;
2616
+ throw new MastraError({
2617
+ id: "MYSQL_PURGE_ITEM_FAILED",
2618
+ domain: ErrorDomain.STORAGE,
2619
+ category: ErrorCategory.THIRD_PARTY
2620
+ }, transactionError);
2621
+ } finally {
2622
+ connection.release();
2623
+ }
2624
+ }
2578
2625
  async getItemById(args) {
2579
2626
  try {
2580
2627
  const tableItemsName = formatTableName(TABLE_DATASET_ITEMS);
@@ -2979,6 +3026,56 @@ var ExperimentsMySQL = class ExperimentsMySQL extends ExperimentsStorage {
2979
3026
  this.#skipDefaultIndexes = skipDefaultIndexes;
2980
3027
  this.#indexes = indexes?.filter((idx) => ExperimentsMySQL.MANAGED_TABLES.includes(idx.table));
2981
3028
  }
3029
+ async #withPurgeBarrier(experimentId, itemId, fn) {
3030
+ const connection = await this.pool.getConnection();
3031
+ try {
3032
+ await connection.beginTransaction();
3033
+ const [experimentRows] = await connection.execute(`SELECT ${quoteIdentifier("datasetId", "column name")} FROM ${formatTableName(TABLE_EXPERIMENTS)} WHERE ${quoteIdentifier("id", "column name")} = ?`, [experimentId]);
3034
+ const datasetId = Array.isArray(experimentRows) ? experimentRows[0]?.datasetId ?? null : null;
3035
+ let purgeMetadata = null;
3036
+ if (datasetId) {
3037
+ await connection.execute(`SELECT ${quoteIdentifier("id", "column name")} FROM ${formatTableName(TABLE_DATASETS)} WHERE ${quoteIdentifier("id", "column name")} = ? FOR UPDATE`, [datasetId]);
3038
+ const [itemRows] = await connection.execute(`SELECT ${quoteIdentifier("metadata", "column name")} FROM ${formatTableName(TABLE_DATASET_ITEMS)} WHERE ${quoteIdentifier("id", "column name")} = ? AND ${quoteIdentifier("datasetId", "column name")} = ?`, [itemId, datasetId]);
3039
+ if (Array.isArray(itemRows)) {
3040
+ const purgedRow = itemRows.find((row) => parseJSON$2(row.metadata)?.__purged === true);
3041
+ purgeMetadata = purgedRow ? parseJSON$2(purgedRow.metadata) ?? null : null;
3042
+ }
3043
+ }
3044
+ const result = await fn(connection, purgeMetadata);
3045
+ await connection.commit();
3046
+ return result;
3047
+ } catch (error) {
3048
+ let transactionError = error;
3049
+ try {
3050
+ await connection.rollback();
3051
+ } catch (rollbackError) {
3052
+ transactionError = new AggregateError([error, rollbackError], "Transaction and rollback both failed");
3053
+ }
3054
+ throw transactionError;
3055
+ } finally {
3056
+ connection.release();
3057
+ }
3058
+ }
3059
+ async #ensureExperimentResultNaturalKey() {
3060
+ const tableName = formatTableName(TABLE_EXPERIMENT_RESULTS);
3061
+ const indexName = "idx_experiment_results_exp_item_attempt";
3062
+ const [indexRows] = await this.pool.execute(`SELECT 1 FROM information_schema.STATISTICS
3063
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?
3064
+ LIMIT 1`, [TABLE_EXPERIMENT_RESULTS, indexName]);
3065
+ if (Array.isArray(indexRows) && indexRows.length > 0) return;
3066
+ try {
3067
+ await this.pool.execute(`CREATE UNIQUE INDEX ${quoteIdentifier(indexName, "index name")} ON ${tableName} (${quoteIdentifier("experimentId", "column name")}(191), ${quoteIdentifier("itemId", "column name")}(191), ((COALESCE(${quoteIdentifier("attempt", "column name")}, 0))))`);
3068
+ } catch (error) {
3069
+ if (hasErrorCode(error, /* @__PURE__ */ new Set([1061, "ER_DUP_KEYNAME"]))) return;
3070
+ if (!hasErrorCode(error, /* @__PURE__ */ new Set([1062, "ER_DUP_ENTRY"]))) throw error;
3071
+ throw new MastraError({
3072
+ id: "MYSQL_EXPERIMENT_RESULT_NATURAL_KEY_MIGRATION_REQUIRED",
3073
+ domain: ErrorDomain.STORAGE,
3074
+ category: ErrorCategory.USER,
3075
+ text: "Could not enforce experiment-result uniqueness. Resolve duplicate experiment, item, and attempt combinations before restarting the MySQL store."
3076
+ }, error);
3077
+ }
3078
+ }
2982
3079
  /**
2983
3080
  * Returns default index definitions for the experiments domain tables.
2984
3081
  */
@@ -3035,6 +3132,7 @@ var ExperimentsMySQL = class ExperimentsMySQL extends ExperimentsStorage {
3035
3132
  "attempt"
3036
3133
  ]
3037
3134
  });
3135
+ await this.#ensureExperimentResultNaturalKey();
3038
3136
  await this.createDefaultIndexes();
3039
3137
  await this.createCustomIndexes();
3040
3138
  }
@@ -3081,7 +3179,7 @@ var ExperimentsMySQL = class ExperimentsMySQL extends ExperimentsStorage {
3081
3179
  itemDatasetVersion: row.itemDatasetVersion ?? null,
3082
3180
  organizationId: row.organizationId ?? null,
3083
3181
  projectId: row.projectId ?? null,
3084
- input: parseJSON$2(row.input),
3182
+ input: row.input === null ? null : parseJSON$2(row.input),
3085
3183
  output: row.output ? parseJSON$2(row.output) : null,
3086
3184
  groundTruth: row.groundTruth ? parseJSON$2(row.groundTruth) : null,
3087
3185
  metadata: row.metadata ? parseJSON$2(row.metadata) ?? null : null,
@@ -3376,29 +3474,51 @@ var ExperimentsMySQL = class ExperimentsMySQL extends ExperimentsStorage {
3376
3474
  try {
3377
3475
  const id = input.id ?? randomUUID();
3378
3476
  const now = /* @__PURE__ */ new Date();
3379
- await this.operations.insert({
3380
- tableName: TABLE_EXPERIMENT_RESULTS,
3381
- record: {
3477
+ const purgeMetadata = await this.#withPurgeBarrier(input.experimentId, input.itemId, async (connection, marker) => {
3478
+ await connection.execute(`INSERT INTO ${formatTableName(TABLE_EXPERIMENT_RESULTS)} (
3479
+ ${[
3480
+ "id",
3481
+ "experimentId",
3482
+ "itemId",
3483
+ "itemDatasetVersion",
3484
+ "organizationId",
3485
+ "projectId",
3486
+ "input",
3487
+ "output",
3488
+ "groundTruth",
3489
+ "metadata",
3490
+ "error",
3491
+ "startedAt",
3492
+ "completedAt",
3493
+ "retryCount",
3494
+ "attempt",
3495
+ "traceId",
3496
+ "status",
3497
+ "tags",
3498
+ "createdAt"
3499
+ ].map((column) => quoteIdentifier(column, "column name")).join(", ")}
3500
+ ) VALUES (${Array.from({ length: 19 }, () => "?").join(", ")})`, [
3382
3501
  id,
3383
- experimentId: input.experimentId,
3384
- itemId: input.itemId,
3385
- itemDatasetVersion: input.itemDatasetVersion ?? null,
3386
- organizationId: input.organizationId ?? null,
3387
- projectId: input.projectId ?? null,
3388
- input: JSON.stringify(input.input),
3389
- output: input.output != null ? JSON.stringify(input.output) : null,
3390
- groundTruth: input.groundTruth != null ? JSON.stringify(input.groundTruth) : null,
3391
- metadata: input.metadata != null ? JSON.stringify(input.metadata) : null,
3392
- error: input.error ? JSON.stringify(input.error) : null,
3393
- startedAt: input.startedAt,
3394
- completedAt: input.completedAt,
3395
- retryCount: input.retryCount,
3396
- attempt: input.attempt ?? 0,
3397
- traceId: input.traceId ?? null,
3398
- status: input.status ?? null,
3399
- tags: input.tags ? JSON.stringify(input.tags) : null,
3400
- createdAt: now
3401
- }
3502
+ input.experimentId,
3503
+ input.itemId,
3504
+ input.itemDatasetVersion ?? null,
3505
+ input.organizationId ?? null,
3506
+ input.projectId ?? null,
3507
+ JSON.stringify(marker ? null : input.input),
3508
+ marker || input.output == null ? null : JSON.stringify(input.output),
3509
+ marker || input.groundTruth == null ? null : JSON.stringify(input.groundTruth),
3510
+ JSON.stringify(marker ?? input.metadata ?? null),
3511
+ marker || input.error == null ? null : JSON.stringify(input.error),
3512
+ input.startedAt,
3513
+ input.completedAt,
3514
+ input.retryCount,
3515
+ input.attempt ?? 0,
3516
+ input.traceId ?? null,
3517
+ input.status ?? null,
3518
+ marker || input.tags == null ? null : JSON.stringify(input.tags),
3519
+ now
3520
+ ]);
3521
+ return marker;
3402
3522
  });
3403
3523
  return {
3404
3524
  id,
@@ -3407,18 +3527,18 @@ var ExperimentsMySQL = class ExperimentsMySQL extends ExperimentsStorage {
3407
3527
  itemDatasetVersion: input.itemDatasetVersion,
3408
3528
  organizationId: input.organizationId ?? null,
3409
3529
  projectId: input.projectId ?? null,
3410
- input: input.input,
3411
- output: input.output,
3412
- groundTruth: input.groundTruth,
3413
- metadata: input.metadata ?? null,
3414
- error: input.error,
3530
+ input: purgeMetadata ? null : input.input,
3531
+ output: purgeMetadata ? null : input.output,
3532
+ groundTruth: purgeMetadata ? null : input.groundTruth,
3533
+ metadata: purgeMetadata ?? input.metadata ?? null,
3534
+ error: purgeMetadata ? null : input.error,
3415
3535
  startedAt: input.startedAt,
3416
3536
  completedAt: input.completedAt,
3417
3537
  retryCount: input.retryCount,
3418
3538
  attempt: input.attempt ?? 0,
3419
3539
  traceId: input.traceId ?? null,
3420
3540
  status: input.status ?? null,
3421
- tags: input.tags ?? null,
3541
+ tags: purgeMetadata ? null : input.tags ?? null,
3422
3542
  createdAt: now
3423
3543
  };
3424
3544
  } catch (error) {
@@ -3457,49 +3577,77 @@ var ExperimentsMySQL = class ExperimentsMySQL extends ExperimentsStorage {
3457
3577
  });
3458
3578
  try {
3459
3579
  const attempt = input.attempt ?? 0;
3460
- const existingId = (await this.operations.query(`SELECT ${quoteIdentifier("id", "column name")} FROM ${formatTableName(TABLE_EXPERIMENT_RESULTS)} WHERE ${quoteIdentifier("experimentId", "column name")} = ? AND ${quoteIdentifier("itemId", "column name")} = ? AND COALESCE(${quoteIdentifier("attempt", "column name")}, 0) = ?`, [
3461
- input.experimentId,
3462
- input.itemId,
3463
- attempt
3464
- ]))[0]?.id;
3465
- if (!existingId) return await this.addExperimentResult({
3466
- ...input,
3467
- attempt
3468
- });
3469
- await this.operations.update({
3470
- tableName: TABLE_EXPERIMENT_RESULTS,
3471
- keys: { id: existingId },
3472
- data: {
3473
- itemDatasetVersion: input.itemDatasetVersion ?? null,
3474
- organizationId: input.organizationId ?? null,
3475
- projectId: input.projectId ?? null,
3476
- input: JSON.stringify(input.input),
3477
- output: input.output != null ? JSON.stringify(input.output) : null,
3478
- groundTruth: input.groundTruth != null ? JSON.stringify(input.groundTruth) : null,
3479
- metadata: input.metadata != null ? JSON.stringify(input.metadata) : null,
3480
- error: input.error ? JSON.stringify(input.error) : null,
3481
- startedAt: input.startedAt,
3482
- completedAt: input.completedAt,
3483
- retryCount: input.retryCount,
3580
+ return await this.#withPurgeBarrier(input.experimentId, input.itemId, async (connection, marker) => {
3581
+ const tableName = formatTableName(TABLE_EXPERIMENT_RESULTS);
3582
+ const updateColumns = [
3583
+ "itemDatasetVersion",
3584
+ "organizationId",
3585
+ "projectId",
3586
+ "input",
3587
+ "output",
3588
+ "groundTruth",
3589
+ "metadata",
3590
+ "error",
3591
+ "startedAt",
3592
+ "completedAt",
3593
+ "retryCount",
3594
+ "attempt",
3595
+ "traceId",
3596
+ "status",
3597
+ "tags"
3598
+ ];
3599
+ const updateValues = [
3600
+ input.itemDatasetVersion ?? null,
3601
+ input.organizationId ?? null,
3602
+ input.projectId ?? null,
3603
+ JSON.stringify(marker ? null : input.input),
3604
+ marker || input.output == null ? null : JSON.stringify(input.output),
3605
+ marker || input.groundTruth == null ? null : JSON.stringify(input.groundTruth),
3606
+ JSON.stringify(marker ?? input.metadata ?? null),
3607
+ marker || input.error == null ? null : JSON.stringify(input.error),
3608
+ input.startedAt,
3609
+ input.completedAt,
3610
+ input.retryCount,
3484
3611
  attempt,
3485
- traceId: input.traceId ?? null,
3486
- status: input.status ?? null,
3487
- tags: input.tags ? JSON.stringify(input.tags) : null
3488
- }
3489
- });
3490
- const updated = await this.getExperimentResultById({ id: existingId });
3491
- if (!updated) throw new MastraError({
3492
- id: "MYSQL_UPSERT_EXPERIMENT_RESULT_NOT_FOUND",
3493
- domain: ErrorDomain.STORAGE,
3494
- category: ErrorCategory.USER,
3495
- text: `Experiment result ${existingId} not found after upsert`,
3496
- details: {
3497
- experimentId: input.experimentId,
3498
- itemId: input.itemId,
3612
+ input.traceId ?? null,
3613
+ input.status ?? null,
3614
+ marker || input.tags == null ? null : JSON.stringify(input.tags)
3615
+ ];
3616
+ const columns = [
3617
+ "id",
3618
+ "experimentId",
3619
+ "itemId",
3620
+ ...updateColumns,
3621
+ "createdAt"
3622
+ ];
3623
+ await connection.execute(`INSERT INTO ${tableName} (${columns.map((column) => quoteIdentifier(column, "column name")).join(", ")})
3624
+ VALUES (${Array.from({ length: columns.length }, () => "?").join(", ")})
3625
+ ON DUPLICATE KEY UPDATE ${updateColumns.map((column) => `${quoteIdentifier(column, "column name")} = VALUES(${quoteIdentifier(column, "column name")})`).join(", ")}`, [
3626
+ randomUUID(),
3627
+ input.experimentId,
3628
+ input.itemId,
3629
+ ...updateValues,
3630
+ /* @__PURE__ */ new Date()
3631
+ ]);
3632
+ const [rows] = await connection.execute(`SELECT * FROM ${tableName} WHERE ${quoteIdentifier("experimentId", "column name")} = ? AND ${quoteIdentifier("itemId", "column name")} = ? AND ${quoteIdentifier("attempt", "column name")} = ?`, [
3633
+ input.experimentId,
3634
+ input.itemId,
3499
3635
  attempt
3500
- }
3636
+ ]);
3637
+ const row = Array.isArray(rows) ? rows[0] : void 0;
3638
+ if (!row) throw new MastraError({
3639
+ id: "MYSQL_UPSERT_EXPERIMENT_RESULT_NOT_FOUND",
3640
+ domain: ErrorDomain.STORAGE,
3641
+ category: ErrorCategory.USER,
3642
+ text: `Experiment result not found after upsert`,
3643
+ details: {
3644
+ experimentId: input.experimentId,
3645
+ itemId: input.itemId,
3646
+ attempt
3647
+ }
3648
+ });
3649
+ return this.mapExperimentResult(row);
3501
3650
  });
3502
- return updated;
3503
3651
  } catch (error) {
3504
3652
  if (error instanceof MastraError) throw error;
3505
3653
  throw new MastraError({
@@ -3517,18 +3665,27 @@ var ExperimentsMySQL = class ExperimentsMySQL extends ExperimentsStorage {
3517
3665
  });
3518
3666
  if (!existing) throw new Error(`Experiment result not found: ${input.id}`);
3519
3667
  if (input.experimentId && existing.experimentId !== input.experimentId) throw new Error(`Experiment result ${input.id} does not belong to experiment ${input.experimentId}`);
3520
- const updateData = {};
3521
- if (input.status !== void 0) updateData.status = input.status;
3522
- if (input.tags !== void 0) updateData.tags = input.tags ? JSON.stringify(input.tags) : null;
3523
- if (input.comment !== void 0) updateData.comment = input.comment;
3524
- if (Object.keys(updateData).length > 0) await this.operations.update({
3525
- tableName: TABLE_EXPERIMENT_RESULTS,
3526
- keys: { id: input.id },
3527
- data: updateData
3668
+ return await this.#withPurgeBarrier(existing.experimentId, existing.itemId, async (connection, marker) => {
3669
+ await connection.execute(`UPDATE ${formatTableName(TABLE_EXPERIMENT_RESULTS)}
3670
+ SET ${quoteIdentifier("status", "column name")} = CASE WHEN ? THEN ? ELSE ${quoteIdentifier("status", "column name")} END,
3671
+ ${quoteIdentifier("tags", "column name")} = CASE WHEN ? THEN NULL WHEN ? THEN ? ELSE ${quoteIdentifier("tags", "column name")} END,
3672
+ ${quoteIdentifier("comment", "column name")} = CASE WHEN ? THEN NULL WHEN ? THEN ? ELSE ${quoteIdentifier("comment", "column name")} END
3673
+ WHERE ${quoteIdentifier("id", "column name")} = ?`, [
3674
+ input.status !== void 0,
3675
+ input.status ?? null,
3676
+ Boolean(marker),
3677
+ input.tags !== void 0,
3678
+ input.tags === void 0 ? null : JSON.stringify(input.tags),
3679
+ Boolean(marker),
3680
+ input.comment !== void 0,
3681
+ input.comment ?? null,
3682
+ input.id
3683
+ ]);
3684
+ const [rows] = await connection.execute(`SELECT * FROM ${formatTableName(TABLE_EXPERIMENT_RESULTS)} WHERE ${quoteIdentifier("id", "column name")} = ?`, [input.id]);
3685
+ const row = Array.isArray(rows) ? rows[0] : void 0;
3686
+ if (!row) throw new Error(`Experiment result ${input.id} not found after update`);
3687
+ return this.mapExperimentResult(row);
3528
3688
  });
3529
- const updated = await this.getExperimentResultById({ id: input.id });
3530
- if (!updated) throw new Error(`Experiment result ${input.id} not found after update`);
3531
- return updated;
3532
3689
  } catch (error) {
3533
3690
  if (error instanceof MastraError) throw error;
3534
3691
  throw new MastraError({