@mastra/mssql 0.0.0-roamin-openaivoice-speak-options-passing-20250926163614 → 0.0.0-safe-stringify-telemetry-20251205024938
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/CHANGELOG.md +221 -3
- package/README.md +315 -36
- package/dist/index.cjs +1464 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1464 -181
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts +44 -0
- package/dist/storage/domains/observability/index.d.ts.map +1 -0
- package/dist/storage/domains/operations/index.d.ts +67 -4
- package/dist/storage/domains/operations/index.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts +11 -2
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/dist/storage/domains/utils.d.ts +19 -0
- package/dist/storage/domains/utils.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +5 -11
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +53 -11
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +14 -7
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
2
|
-
import { MastraStorage, LegacyEvalsStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT,
|
|
3
|
-
import
|
|
2
|
+
import { MastraStorage, LegacyEvalsStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_TRACES, TABLE_EVALS, TABLE_SCORERS, TABLE_AI_SPANS, ScoresStorage, TracesStorage, WorkflowsStorage, MemoryStorage, resolveMessageLimit, TABLE_RESOURCES, ObservabilityStorage, safelyParseJSON } from '@mastra/core/storage';
|
|
3
|
+
import sql3 from 'mssql';
|
|
4
4
|
import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
|
|
5
5
|
import { MessageList } from '@mastra/core/agent';
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
7
|
+
import { saveScorePayloadSchema } from '@mastra/core/scores';
|
|
6
8
|
|
|
7
9
|
// src/storage/index.ts
|
|
8
10
|
function getSchemaName(schema) {
|
|
@@ -14,6 +16,69 @@ function getTableName({ indexName, schemaName }) {
|
|
|
14
16
|
const quotedSchemaName = schemaName;
|
|
15
17
|
return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
|
|
16
18
|
}
|
|
19
|
+
function buildDateRangeFilter(dateRange, fieldName) {
|
|
20
|
+
const filters = {};
|
|
21
|
+
if (dateRange?.start) {
|
|
22
|
+
filters[`${fieldName}_gte`] = dateRange.start;
|
|
23
|
+
}
|
|
24
|
+
if (dateRange?.end) {
|
|
25
|
+
filters[`${fieldName}_lte`] = dateRange.end;
|
|
26
|
+
}
|
|
27
|
+
return filters;
|
|
28
|
+
}
|
|
29
|
+
function prepareWhereClause(filters, _schema) {
|
|
30
|
+
const conditions = [];
|
|
31
|
+
const params = {};
|
|
32
|
+
let paramIndex = 1;
|
|
33
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
34
|
+
if (value === void 0) return;
|
|
35
|
+
const paramName = `p${paramIndex++}`;
|
|
36
|
+
if (key.endsWith("_gte")) {
|
|
37
|
+
const fieldName = key.slice(0, -4);
|
|
38
|
+
conditions.push(`[${parseSqlIdentifier(fieldName, "field name")}] >= @${paramName}`);
|
|
39
|
+
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
40
|
+
} else if (key.endsWith("_lte")) {
|
|
41
|
+
const fieldName = key.slice(0, -4);
|
|
42
|
+
conditions.push(`[${parseSqlIdentifier(fieldName, "field name")}] <= @${paramName}`);
|
|
43
|
+
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
44
|
+
} else if (value === null) {
|
|
45
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] IS NULL`);
|
|
46
|
+
} else {
|
|
47
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] = @${paramName}`);
|
|
48
|
+
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
|
|
53
|
+
params
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function transformFromSqlRow({
|
|
57
|
+
tableName,
|
|
58
|
+
sqlRow
|
|
59
|
+
}) {
|
|
60
|
+
const schema = TABLE_SCHEMAS[tableName];
|
|
61
|
+
const result = {};
|
|
62
|
+
Object.entries(sqlRow).forEach(([key, value]) => {
|
|
63
|
+
const columnSchema = schema?.[key];
|
|
64
|
+
if (columnSchema?.type === "jsonb" && typeof value === "string") {
|
|
65
|
+
try {
|
|
66
|
+
result[key] = JSON.parse(value);
|
|
67
|
+
} catch {
|
|
68
|
+
result[key] = value;
|
|
69
|
+
}
|
|
70
|
+
} else if (columnSchema?.type === "timestamp" && value && typeof value === "string") {
|
|
71
|
+
result[key] = new Date(value);
|
|
72
|
+
} else if (columnSchema?.type === "timestamp" && value instanceof Date) {
|
|
73
|
+
result[key] = value;
|
|
74
|
+
} else if (columnSchema?.type === "boolean") {
|
|
75
|
+
result[key] = Boolean(value);
|
|
76
|
+
} else {
|
|
77
|
+
result[key] = value;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
17
82
|
|
|
18
83
|
// src/storage/domains/legacy-evals/index.ts
|
|
19
84
|
function transformEvalRow(row) {
|
|
@@ -24,7 +89,7 @@ function transformEvalRow(row) {
|
|
|
24
89
|
} catch {
|
|
25
90
|
}
|
|
26
91
|
}
|
|
27
|
-
if (row.
|
|
92
|
+
if (row.result) {
|
|
28
93
|
try {
|
|
29
94
|
resultValue = typeof row.result === "string" ? JSON.parse(row.result) : row.result;
|
|
30
95
|
} catch {
|
|
@@ -70,7 +135,7 @@ var LegacyEvalsMSSQL = class extends LegacyEvalsStorage {
|
|
|
70
135
|
if (error && error.number === 208 && error.message && error.message.includes("Invalid object name")) {
|
|
71
136
|
return [];
|
|
72
137
|
}
|
|
73
|
-
|
|
138
|
+
this.logger?.error?.("Failed to get evals for the specified agent:", error);
|
|
74
139
|
throw error;
|
|
75
140
|
}
|
|
76
141
|
}
|
|
@@ -106,7 +171,7 @@ var LegacyEvalsMSSQL = class extends LegacyEvalsStorage {
|
|
|
106
171
|
const countReq = this.pool.request();
|
|
107
172
|
Object.entries(params).forEach(([key, value]) => {
|
|
108
173
|
if (value instanceof Date) {
|
|
109
|
-
countReq.input(key,
|
|
174
|
+
countReq.input(key, sql3.DateTime, value);
|
|
110
175
|
} else {
|
|
111
176
|
countReq.input(key, value);
|
|
112
177
|
}
|
|
@@ -125,7 +190,7 @@ var LegacyEvalsMSSQL = class extends LegacyEvalsStorage {
|
|
|
125
190
|
const req = this.pool.request();
|
|
126
191
|
Object.entries(params).forEach(([key, value]) => {
|
|
127
192
|
if (value instanceof Date) {
|
|
128
|
-
req.input(key,
|
|
193
|
+
req.input(key, sql3.DateTime, value);
|
|
129
194
|
} else {
|
|
130
195
|
req.input(key, value);
|
|
131
196
|
}
|
|
@@ -157,7 +222,7 @@ var LegacyEvalsMSSQL = class extends LegacyEvalsStorage {
|
|
|
157
222
|
error
|
|
158
223
|
);
|
|
159
224
|
this.logger?.error?.(mastraError.toString());
|
|
160
|
-
this.logger?.trackException(mastraError);
|
|
225
|
+
this.logger?.trackException?.(mastraError);
|
|
161
226
|
throw mastraError;
|
|
162
227
|
}
|
|
163
228
|
}
|
|
@@ -250,7 +315,8 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
250
315
|
};
|
|
251
316
|
}
|
|
252
317
|
const orderByField = orderBy === "createdAt" ? "[createdAt]" : "[updatedAt]";
|
|
253
|
-
const
|
|
318
|
+
const dir = (sortDirection || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
|
|
319
|
+
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
254
320
|
const dataRequest = this.pool.request();
|
|
255
321
|
dataRequest.input("resourceId", resourceId);
|
|
256
322
|
dataRequest.input("perPage", perPage);
|
|
@@ -307,9 +373,14 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
307
373
|
req.input("id", thread.id);
|
|
308
374
|
req.input("resourceId", thread.resourceId);
|
|
309
375
|
req.input("title", thread.title);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
376
|
+
const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
|
|
377
|
+
if (metadata === null) {
|
|
378
|
+
req.input("metadata", sql3.NVarChar, null);
|
|
379
|
+
} else {
|
|
380
|
+
req.input("metadata", metadata);
|
|
381
|
+
}
|
|
382
|
+
req.input("createdAt", sql3.DateTime2, thread.createdAt);
|
|
383
|
+
req.input("updatedAt", sql3.DateTime2, thread.updatedAt);
|
|
313
384
|
await req.query(mergeSql);
|
|
314
385
|
return thread;
|
|
315
386
|
} catch (error) {
|
|
@@ -334,7 +405,8 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
334
405
|
try {
|
|
335
406
|
const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
|
|
336
407
|
const orderByField = orderBy === "createdAt" ? "[createdAt]" : "[updatedAt]";
|
|
337
|
-
const
|
|
408
|
+
const dir = (sortDirection || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
|
|
409
|
+
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir}`;
|
|
338
410
|
const request = this.pool.request();
|
|
339
411
|
request.input("resourceId", resourceId);
|
|
340
412
|
const resultSet = await request.query(dataQuery);
|
|
@@ -583,7 +655,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
583
655
|
error
|
|
584
656
|
);
|
|
585
657
|
this.logger?.error?.(mastraError.toString());
|
|
586
|
-
this.logger?.trackException(mastraError);
|
|
658
|
+
this.logger?.trackException?.(mastraError);
|
|
587
659
|
return [];
|
|
588
660
|
}
|
|
589
661
|
}
|
|
@@ -623,7 +695,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
623
695
|
error
|
|
624
696
|
);
|
|
625
697
|
this.logger?.error?.(mastraError.toString());
|
|
626
|
-
this.logger?.trackException(mastraError);
|
|
698
|
+
this.logger?.trackException?.(mastraError);
|
|
627
699
|
return [];
|
|
628
700
|
}
|
|
629
701
|
}
|
|
@@ -682,10 +754,11 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
682
754
|
const rows = rowsResult.recordset || [];
|
|
683
755
|
rows.sort((a, b) => a.seq_id - b.seq_id);
|
|
684
756
|
messages.push(...rows);
|
|
685
|
-
|
|
757
|
+
let parsed = this._parseAndFormatMessages(messages, format);
|
|
758
|
+
parsed = parsed.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
686
759
|
return {
|
|
687
760
|
messages: parsed,
|
|
688
|
-
total
|
|
761
|
+
total,
|
|
689
762
|
page,
|
|
690
763
|
perPage,
|
|
691
764
|
hasMore: currentOffset + rows.length < total
|
|
@@ -705,7 +778,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
705
778
|
error
|
|
706
779
|
);
|
|
707
780
|
this.logger?.error?.(mastraError.toString());
|
|
708
|
-
this.logger?.trackException(mastraError);
|
|
781
|
+
this.logger?.trackException?.(mastraError);
|
|
709
782
|
return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
|
|
710
783
|
}
|
|
711
784
|
}
|
|
@@ -757,7 +830,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
757
830
|
"content",
|
|
758
831
|
typeof message.content === "string" ? message.content : JSON.stringify(message.content)
|
|
759
832
|
);
|
|
760
|
-
request.input("createdAt",
|
|
833
|
+
request.input("createdAt", sql3.DateTime2, message.createdAt);
|
|
761
834
|
request.input("role", message.role);
|
|
762
835
|
request.input("type", message.type || "v2");
|
|
763
836
|
request.input("resourceId", message.resourceId);
|
|
@@ -776,7 +849,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
776
849
|
await request.query(mergeSql);
|
|
777
850
|
}
|
|
778
851
|
const threadReq = transaction.request();
|
|
779
|
-
threadReq.input("updatedAt",
|
|
852
|
+
threadReq.input("updatedAt", sql3.DateTime2, /* @__PURE__ */ new Date());
|
|
780
853
|
threadReq.input("id", threadId);
|
|
781
854
|
await threadReq.query(`UPDATE ${tableThreads} SET [updatedAt] = @updatedAt WHERE id = @id`);
|
|
782
855
|
await transaction.commit();
|
|
@@ -972,8 +1045,10 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
972
1045
|
return null;
|
|
973
1046
|
}
|
|
974
1047
|
return {
|
|
975
|
-
|
|
976
|
-
|
|
1048
|
+
id: result.id,
|
|
1049
|
+
createdAt: result.createdAt,
|
|
1050
|
+
updatedAt: result.updatedAt,
|
|
1051
|
+
workingMemory: result.workingMemory,
|
|
977
1052
|
metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
|
|
978
1053
|
};
|
|
979
1054
|
} catch (error) {
|
|
@@ -987,7 +1062,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
987
1062
|
error
|
|
988
1063
|
);
|
|
989
1064
|
this.logger?.error?.(mastraError.toString());
|
|
990
|
-
this.logger?.trackException(mastraError);
|
|
1065
|
+
this.logger?.trackException?.(mastraError);
|
|
991
1066
|
throw mastraError;
|
|
992
1067
|
}
|
|
993
1068
|
}
|
|
@@ -996,7 +1071,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
996
1071
|
tableName: TABLE_RESOURCES,
|
|
997
1072
|
record: {
|
|
998
1073
|
...resource,
|
|
999
|
-
metadata:
|
|
1074
|
+
metadata: resource.metadata
|
|
1000
1075
|
}
|
|
1001
1076
|
});
|
|
1002
1077
|
return resource;
|
|
@@ -1054,20 +1129,337 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
1054
1129
|
error
|
|
1055
1130
|
);
|
|
1056
1131
|
this.logger?.error?.(mastraError.toString());
|
|
1057
|
-
this.logger?.trackException(mastraError);
|
|
1132
|
+
this.logger?.trackException?.(mastraError);
|
|
1058
1133
|
throw mastraError;
|
|
1059
1134
|
}
|
|
1060
1135
|
}
|
|
1061
1136
|
};
|
|
1137
|
+
var ObservabilityMSSQL = class extends ObservabilityStorage {
|
|
1138
|
+
pool;
|
|
1139
|
+
operations;
|
|
1140
|
+
schema;
|
|
1141
|
+
constructor({
|
|
1142
|
+
pool,
|
|
1143
|
+
operations,
|
|
1144
|
+
schema
|
|
1145
|
+
}) {
|
|
1146
|
+
super();
|
|
1147
|
+
this.pool = pool;
|
|
1148
|
+
this.operations = operations;
|
|
1149
|
+
this.schema = schema;
|
|
1150
|
+
}
|
|
1151
|
+
get aiTracingStrategy() {
|
|
1152
|
+
return {
|
|
1153
|
+
preferred: "batch-with-updates",
|
|
1154
|
+
supported: ["batch-with-updates", "insert-only"]
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
async createAISpan(span) {
|
|
1158
|
+
try {
|
|
1159
|
+
const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt;
|
|
1160
|
+
const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt;
|
|
1161
|
+
const record = {
|
|
1162
|
+
...span,
|
|
1163
|
+
startedAt,
|
|
1164
|
+
endedAt
|
|
1165
|
+
// Note: createdAt/updatedAt will be set by default values
|
|
1166
|
+
};
|
|
1167
|
+
return this.operations.insert({ tableName: TABLE_AI_SPANS, record });
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
throw new MastraError(
|
|
1170
|
+
{
|
|
1171
|
+
id: "MSSQL_STORE_CREATE_AI_SPAN_FAILED",
|
|
1172
|
+
domain: ErrorDomain.STORAGE,
|
|
1173
|
+
category: ErrorCategory.USER,
|
|
1174
|
+
details: {
|
|
1175
|
+
spanId: span.spanId,
|
|
1176
|
+
traceId: span.traceId,
|
|
1177
|
+
spanType: span.spanType,
|
|
1178
|
+
spanName: span.name
|
|
1179
|
+
}
|
|
1180
|
+
},
|
|
1181
|
+
error
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
async getAITrace(traceId) {
|
|
1186
|
+
try {
|
|
1187
|
+
const tableName = getTableName({
|
|
1188
|
+
indexName: TABLE_AI_SPANS,
|
|
1189
|
+
schemaName: getSchemaName(this.schema)
|
|
1190
|
+
});
|
|
1191
|
+
const request = this.pool.request();
|
|
1192
|
+
request.input("traceId", traceId);
|
|
1193
|
+
const result = await request.query(
|
|
1194
|
+
`SELECT
|
|
1195
|
+
[traceId], [spanId], [parentSpanId], [name], [scope], [spanType],
|
|
1196
|
+
[attributes], [metadata], [links], [input], [output], [error], [isEvent],
|
|
1197
|
+
[startedAt], [endedAt], [createdAt], [updatedAt]
|
|
1198
|
+
FROM ${tableName}
|
|
1199
|
+
WHERE [traceId] = @traceId
|
|
1200
|
+
ORDER BY [startedAt] DESC`
|
|
1201
|
+
);
|
|
1202
|
+
if (!result.recordset || result.recordset.length === 0) {
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
return {
|
|
1206
|
+
traceId,
|
|
1207
|
+
spans: result.recordset.map(
|
|
1208
|
+
(span) => transformFromSqlRow({
|
|
1209
|
+
tableName: TABLE_AI_SPANS,
|
|
1210
|
+
sqlRow: span
|
|
1211
|
+
})
|
|
1212
|
+
)
|
|
1213
|
+
};
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
throw new MastraError(
|
|
1216
|
+
{
|
|
1217
|
+
id: "MSSQL_STORE_GET_AI_TRACE_FAILED",
|
|
1218
|
+
domain: ErrorDomain.STORAGE,
|
|
1219
|
+
category: ErrorCategory.USER,
|
|
1220
|
+
details: {
|
|
1221
|
+
traceId
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
error
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async updateAISpan({
|
|
1229
|
+
spanId,
|
|
1230
|
+
traceId,
|
|
1231
|
+
updates
|
|
1232
|
+
}) {
|
|
1233
|
+
try {
|
|
1234
|
+
const data = { ...updates };
|
|
1235
|
+
if (data.endedAt instanceof Date) {
|
|
1236
|
+
data.endedAt = data.endedAt.toISOString();
|
|
1237
|
+
}
|
|
1238
|
+
if (data.startedAt instanceof Date) {
|
|
1239
|
+
data.startedAt = data.startedAt.toISOString();
|
|
1240
|
+
}
|
|
1241
|
+
await this.operations.update({
|
|
1242
|
+
tableName: TABLE_AI_SPANS,
|
|
1243
|
+
keys: { spanId, traceId },
|
|
1244
|
+
data
|
|
1245
|
+
});
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
throw new MastraError(
|
|
1248
|
+
{
|
|
1249
|
+
id: "MSSQL_STORE_UPDATE_AI_SPAN_FAILED",
|
|
1250
|
+
domain: ErrorDomain.STORAGE,
|
|
1251
|
+
category: ErrorCategory.USER,
|
|
1252
|
+
details: {
|
|
1253
|
+
spanId,
|
|
1254
|
+
traceId
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
error
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
async getAITracesPaginated({
|
|
1262
|
+
filters,
|
|
1263
|
+
pagination
|
|
1264
|
+
}) {
|
|
1265
|
+
const page = pagination?.page ?? 0;
|
|
1266
|
+
const perPage = pagination?.perPage ?? 10;
|
|
1267
|
+
const { entityId, entityType, ...actualFilters } = filters || {};
|
|
1268
|
+
const filtersWithDateRange = {
|
|
1269
|
+
...actualFilters,
|
|
1270
|
+
...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
|
|
1271
|
+
parentSpanId: null
|
|
1272
|
+
// Only get root spans for traces
|
|
1273
|
+
};
|
|
1274
|
+
const whereClause = prepareWhereClause(filtersWithDateRange);
|
|
1275
|
+
let actualWhereClause = whereClause.sql;
|
|
1276
|
+
const params = { ...whereClause.params };
|
|
1277
|
+
let currentParamIndex = Object.keys(params).length + 1;
|
|
1278
|
+
if (entityId && entityType) {
|
|
1279
|
+
let name = "";
|
|
1280
|
+
if (entityType === "workflow") {
|
|
1281
|
+
name = `workflow run: '${entityId}'`;
|
|
1282
|
+
} else if (entityType === "agent") {
|
|
1283
|
+
name = `agent run: '${entityId}'`;
|
|
1284
|
+
} else {
|
|
1285
|
+
const error = new MastraError({
|
|
1286
|
+
id: "MSSQL_STORE_GET_AI_TRACES_PAGINATED_FAILED",
|
|
1287
|
+
domain: ErrorDomain.STORAGE,
|
|
1288
|
+
category: ErrorCategory.USER,
|
|
1289
|
+
details: {
|
|
1290
|
+
entityType
|
|
1291
|
+
},
|
|
1292
|
+
text: `Cannot filter by entity type: ${entityType}`
|
|
1293
|
+
});
|
|
1294
|
+
throw error;
|
|
1295
|
+
}
|
|
1296
|
+
const entityParam = `p${currentParamIndex++}`;
|
|
1297
|
+
if (actualWhereClause) {
|
|
1298
|
+
actualWhereClause += ` AND [name] = @${entityParam}`;
|
|
1299
|
+
} else {
|
|
1300
|
+
actualWhereClause = ` WHERE [name] = @${entityParam}`;
|
|
1301
|
+
}
|
|
1302
|
+
params[entityParam] = name;
|
|
1303
|
+
}
|
|
1304
|
+
const tableName = getTableName({
|
|
1305
|
+
indexName: TABLE_AI_SPANS,
|
|
1306
|
+
schemaName: getSchemaName(this.schema)
|
|
1307
|
+
});
|
|
1308
|
+
try {
|
|
1309
|
+
const countRequest = this.pool.request();
|
|
1310
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1311
|
+
countRequest.input(key, value);
|
|
1312
|
+
});
|
|
1313
|
+
const countResult = await countRequest.query(
|
|
1314
|
+
`SELECT COUNT(*) as count FROM ${tableName}${actualWhereClause}`
|
|
1315
|
+
);
|
|
1316
|
+
const total = countResult.recordset[0]?.count ?? 0;
|
|
1317
|
+
if (total === 0) {
|
|
1318
|
+
return {
|
|
1319
|
+
pagination: {
|
|
1320
|
+
total: 0,
|
|
1321
|
+
page,
|
|
1322
|
+
perPage,
|
|
1323
|
+
hasMore: false
|
|
1324
|
+
},
|
|
1325
|
+
spans: []
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
const dataRequest = this.pool.request();
|
|
1329
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1330
|
+
dataRequest.input(key, value);
|
|
1331
|
+
});
|
|
1332
|
+
dataRequest.input("offset", page * perPage);
|
|
1333
|
+
dataRequest.input("limit", perPage);
|
|
1334
|
+
const dataResult = await dataRequest.query(
|
|
1335
|
+
`SELECT * FROM ${tableName}${actualWhereClause} ORDER BY [startedAt] DESC OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
|
|
1336
|
+
);
|
|
1337
|
+
const spans = dataResult.recordset.map(
|
|
1338
|
+
(row) => transformFromSqlRow({
|
|
1339
|
+
tableName: TABLE_AI_SPANS,
|
|
1340
|
+
sqlRow: row
|
|
1341
|
+
})
|
|
1342
|
+
);
|
|
1343
|
+
return {
|
|
1344
|
+
pagination: {
|
|
1345
|
+
total,
|
|
1346
|
+
page,
|
|
1347
|
+
perPage,
|
|
1348
|
+
hasMore: (page + 1) * perPage < total
|
|
1349
|
+
},
|
|
1350
|
+
spans
|
|
1351
|
+
};
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
throw new MastraError(
|
|
1354
|
+
{
|
|
1355
|
+
id: "MSSQL_STORE_GET_AI_TRACES_PAGINATED_FAILED",
|
|
1356
|
+
domain: ErrorDomain.STORAGE,
|
|
1357
|
+
category: ErrorCategory.USER
|
|
1358
|
+
},
|
|
1359
|
+
error
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
async batchCreateAISpans(args) {
|
|
1364
|
+
if (!args.records || args.records.length === 0) {
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
try {
|
|
1368
|
+
await this.operations.batchInsert({
|
|
1369
|
+
tableName: TABLE_AI_SPANS,
|
|
1370
|
+
records: args.records.map((span) => ({
|
|
1371
|
+
...span,
|
|
1372
|
+
startedAt: span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt,
|
|
1373
|
+
endedAt: span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt
|
|
1374
|
+
}))
|
|
1375
|
+
});
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
throw new MastraError(
|
|
1378
|
+
{
|
|
1379
|
+
id: "MSSQL_STORE_BATCH_CREATE_AI_SPANS_FAILED",
|
|
1380
|
+
domain: ErrorDomain.STORAGE,
|
|
1381
|
+
category: ErrorCategory.USER,
|
|
1382
|
+
details: {
|
|
1383
|
+
count: args.records.length
|
|
1384
|
+
}
|
|
1385
|
+
},
|
|
1386
|
+
error
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
async batchUpdateAISpans(args) {
|
|
1391
|
+
if (!args.records || args.records.length === 0) {
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
try {
|
|
1395
|
+
const updates = args.records.map(({ traceId, spanId, updates: data }) => {
|
|
1396
|
+
const processedData = { ...data };
|
|
1397
|
+
if (processedData.endedAt instanceof Date) {
|
|
1398
|
+
processedData.endedAt = processedData.endedAt.toISOString();
|
|
1399
|
+
}
|
|
1400
|
+
if (processedData.startedAt instanceof Date) {
|
|
1401
|
+
processedData.startedAt = processedData.startedAt.toISOString();
|
|
1402
|
+
}
|
|
1403
|
+
return {
|
|
1404
|
+
keys: { spanId, traceId },
|
|
1405
|
+
data: processedData
|
|
1406
|
+
};
|
|
1407
|
+
});
|
|
1408
|
+
await this.operations.batchUpdate({
|
|
1409
|
+
tableName: TABLE_AI_SPANS,
|
|
1410
|
+
updates
|
|
1411
|
+
});
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
throw new MastraError(
|
|
1414
|
+
{
|
|
1415
|
+
id: "MSSQL_STORE_BATCH_UPDATE_AI_SPANS_FAILED",
|
|
1416
|
+
domain: ErrorDomain.STORAGE,
|
|
1417
|
+
category: ErrorCategory.USER,
|
|
1418
|
+
details: {
|
|
1419
|
+
count: args.records.length
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
error
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
async batchDeleteAITraces(args) {
|
|
1427
|
+
if (!args.traceIds || args.traceIds.length === 0) {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
try {
|
|
1431
|
+
const keys = args.traceIds.map((traceId) => ({ traceId }));
|
|
1432
|
+
await this.operations.batchDelete({
|
|
1433
|
+
tableName: TABLE_AI_SPANS,
|
|
1434
|
+
keys
|
|
1435
|
+
});
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
throw new MastraError(
|
|
1438
|
+
{
|
|
1439
|
+
id: "MSSQL_STORE_BATCH_DELETE_AI_TRACES_FAILED",
|
|
1440
|
+
domain: ErrorDomain.STORAGE,
|
|
1441
|
+
category: ErrorCategory.USER,
|
|
1442
|
+
details: {
|
|
1443
|
+
count: args.traceIds.length
|
|
1444
|
+
}
|
|
1445
|
+
},
|
|
1446
|
+
error
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1062
1451
|
var StoreOperationsMSSQL = class extends StoreOperations {
|
|
1063
1452
|
pool;
|
|
1064
1453
|
schemaName;
|
|
1065
1454
|
setupSchemaPromise = null;
|
|
1066
1455
|
schemaSetupComplete = void 0;
|
|
1067
|
-
getSqlType(type, isPrimaryKey = false) {
|
|
1456
|
+
getSqlType(type, isPrimaryKey = false, useLargeStorage = false) {
|
|
1068
1457
|
switch (type) {
|
|
1069
1458
|
case "text":
|
|
1070
|
-
|
|
1459
|
+
if (useLargeStorage) {
|
|
1460
|
+
return "NVARCHAR(MAX)";
|
|
1461
|
+
}
|
|
1462
|
+
return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(400)";
|
|
1071
1463
|
case "timestamp":
|
|
1072
1464
|
return "DATETIME2(7)";
|
|
1073
1465
|
case "uuid":
|
|
@@ -1080,6 +1472,8 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1080
1472
|
return "BIGINT";
|
|
1081
1473
|
case "float":
|
|
1082
1474
|
return "FLOAT";
|
|
1475
|
+
case "boolean":
|
|
1476
|
+
return "BIT";
|
|
1083
1477
|
default:
|
|
1084
1478
|
throw new MastraError({
|
|
1085
1479
|
id: "MASTRA_STORAGE_MSSQL_STORE_TYPE_NOT_SUPPORTED",
|
|
@@ -1142,20 +1536,26 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1142
1536
|
}
|
|
1143
1537
|
await this.setupSchemaPromise;
|
|
1144
1538
|
}
|
|
1145
|
-
async insert({
|
|
1539
|
+
async insert({
|
|
1540
|
+
tableName,
|
|
1541
|
+
record,
|
|
1542
|
+
transaction
|
|
1543
|
+
}) {
|
|
1146
1544
|
try {
|
|
1147
|
-
const columns = Object.keys(record)
|
|
1148
|
-
const
|
|
1149
|
-
const paramNames =
|
|
1150
|
-
const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${
|
|
1151
|
-
const request = this.pool.request();
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
request.input(`param${i}`,
|
|
1545
|
+
const columns = Object.keys(record);
|
|
1546
|
+
const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
|
|
1547
|
+
const paramNames = columns.map((_, i) => `@param${i}`);
|
|
1548
|
+
const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${parsedColumns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
|
|
1549
|
+
const request = transaction ? transaction.request() : this.pool.request();
|
|
1550
|
+
columns.forEach((col, i) => {
|
|
1551
|
+
const value = record[col];
|
|
1552
|
+
const preparedValue = this.prepareValue(value, col, tableName);
|
|
1553
|
+
if (preparedValue instanceof Date) {
|
|
1554
|
+
request.input(`param${i}`, sql3.DateTime2, preparedValue);
|
|
1555
|
+
} else if (preparedValue === null || preparedValue === void 0) {
|
|
1556
|
+
request.input(`param${i}`, this.getMssqlType(tableName, col), null);
|
|
1157
1557
|
} else {
|
|
1158
|
-
request.input(`param${i}`,
|
|
1558
|
+
request.input(`param${i}`, preparedValue);
|
|
1159
1559
|
}
|
|
1160
1560
|
});
|
|
1161
1561
|
await request.query(insertSql);
|
|
@@ -1179,7 +1579,7 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1179
1579
|
try {
|
|
1180
1580
|
await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
|
|
1181
1581
|
} catch (truncateError) {
|
|
1182
|
-
if (truncateError
|
|
1582
|
+
if (truncateError?.number === 4712) {
|
|
1183
1583
|
await this.pool.request().query(`DELETE FROM ${fullTableName}`);
|
|
1184
1584
|
} else {
|
|
1185
1585
|
throw truncateError;
|
|
@@ -1202,9 +1602,11 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1202
1602
|
getDefaultValue(type) {
|
|
1203
1603
|
switch (type) {
|
|
1204
1604
|
case "timestamp":
|
|
1205
|
-
return "DEFAULT
|
|
1605
|
+
return "DEFAULT SYSUTCDATETIME()";
|
|
1206
1606
|
case "jsonb":
|
|
1207
1607
|
return "DEFAULT N'{}'";
|
|
1608
|
+
case "boolean":
|
|
1609
|
+
return "DEFAULT 0";
|
|
1208
1610
|
default:
|
|
1209
1611
|
return super.getDefaultValue(type);
|
|
1210
1612
|
}
|
|
@@ -1215,13 +1617,29 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1215
1617
|
}) {
|
|
1216
1618
|
try {
|
|
1217
1619
|
const uniqueConstraintColumns = tableName === TABLE_WORKFLOW_SNAPSHOT ? ["workflow_name", "run_id"] : [];
|
|
1620
|
+
const largeDataColumns = [
|
|
1621
|
+
"workingMemory",
|
|
1622
|
+
"snapshot",
|
|
1623
|
+
"metadata",
|
|
1624
|
+
"content",
|
|
1625
|
+
// messages.content - can be very long conversation content
|
|
1626
|
+
"input",
|
|
1627
|
+
// evals.input - test input data
|
|
1628
|
+
"output",
|
|
1629
|
+
// evals.output - test output data
|
|
1630
|
+
"instructions",
|
|
1631
|
+
// evals.instructions - evaluation instructions
|
|
1632
|
+
"other"
|
|
1633
|
+
// traces.other - additional trace data
|
|
1634
|
+
];
|
|
1218
1635
|
const columns = Object.entries(schema).map(([name, def]) => {
|
|
1219
1636
|
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1220
1637
|
const constraints = [];
|
|
1221
1638
|
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
1222
1639
|
if (!def.nullable) constraints.push("NOT NULL");
|
|
1223
1640
|
const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
|
|
1224
|
-
|
|
1641
|
+
const useLargeStorage = largeDataColumns.includes(name);
|
|
1642
|
+
return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
|
|
1225
1643
|
}).join(",\n");
|
|
1226
1644
|
if (this.schemaName) {
|
|
1227
1645
|
await this.setupSchema();
|
|
@@ -1308,7 +1726,19 @@ ${columns}
|
|
|
1308
1726
|
const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
|
|
1309
1727
|
if (!columnExists) {
|
|
1310
1728
|
const columnDef = schema[columnName];
|
|
1311
|
-
const
|
|
1729
|
+
const largeDataColumns = [
|
|
1730
|
+
"workingMemory",
|
|
1731
|
+
"snapshot",
|
|
1732
|
+
"metadata",
|
|
1733
|
+
"content",
|
|
1734
|
+
"input",
|
|
1735
|
+
"output",
|
|
1736
|
+
"instructions",
|
|
1737
|
+
"other"
|
|
1738
|
+
];
|
|
1739
|
+
const useLargeStorage = largeDataColumns.includes(columnName);
|
|
1740
|
+
const isIndexed = !!columnDef.primaryKey;
|
|
1741
|
+
const sqlType = this.getSqlType(columnDef.type, isIndexed, useLargeStorage);
|
|
1312
1742
|
const nullable = columnDef.nullable === false ? "NOT NULL" : "";
|
|
1313
1743
|
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
|
|
1314
1744
|
const parsedColumnName = parseSqlIdentifier(columnName, "column name");
|
|
@@ -1336,11 +1766,15 @@ ${columns}
|
|
|
1336
1766
|
try {
|
|
1337
1767
|
const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
|
|
1338
1768
|
const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
|
|
1339
|
-
const values = keyEntries.map(([_, value]) => value);
|
|
1340
1769
|
const sql7 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
|
|
1341
1770
|
const request = this.pool.request();
|
|
1342
|
-
|
|
1343
|
-
|
|
1771
|
+
keyEntries.forEach(([key, value], i) => {
|
|
1772
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1773
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1774
|
+
request.input(`param${i}`, this.getMssqlType(tableName, key), null);
|
|
1775
|
+
} else {
|
|
1776
|
+
request.input(`param${i}`, preparedValue);
|
|
1777
|
+
}
|
|
1344
1778
|
});
|
|
1345
1779
|
const resultSet = await request.query(sql7);
|
|
1346
1780
|
const result = resultSet.recordset[0] || null;
|
|
@@ -1374,7 +1808,7 @@ ${columns}
|
|
|
1374
1808
|
try {
|
|
1375
1809
|
await transaction.begin();
|
|
1376
1810
|
for (const record of records) {
|
|
1377
|
-
await this.insert({ tableName, record });
|
|
1811
|
+
await this.insert({ tableName, record, transaction });
|
|
1378
1812
|
}
|
|
1379
1813
|
await transaction.commit();
|
|
1380
1814
|
} catch (error) {
|
|
@@ -1411,70 +1845,638 @@ ${columns}
|
|
|
1411
1845
|
);
|
|
1412
1846
|
}
|
|
1413
1847
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
operations;
|
|
1441
|
-
schema;
|
|
1442
|
-
constructor({
|
|
1443
|
-
pool,
|
|
1444
|
-
operations,
|
|
1445
|
-
schema
|
|
1446
|
-
}) {
|
|
1447
|
-
super();
|
|
1448
|
-
this.pool = pool;
|
|
1449
|
-
this.operations = operations;
|
|
1450
|
-
this.schema = schema;
|
|
1451
|
-
}
|
|
1452
|
-
async getScoreById({ id }) {
|
|
1453
|
-
try {
|
|
1454
|
-
const request = this.pool.request();
|
|
1455
|
-
request.input("p1", id);
|
|
1456
|
-
const result = await request.query(
|
|
1457
|
-
`SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE id = @p1`
|
|
1458
|
-
);
|
|
1459
|
-
if (result.recordset.length === 0) {
|
|
1460
|
-
return null;
|
|
1848
|
+
/**
|
|
1849
|
+
* Prepares a value for database operations, handling Date objects and JSON serialization
|
|
1850
|
+
*/
|
|
1851
|
+
prepareValue(value, columnName, tableName) {
|
|
1852
|
+
if (value === null || value === void 0) {
|
|
1853
|
+
return value;
|
|
1854
|
+
}
|
|
1855
|
+
if (value instanceof Date) {
|
|
1856
|
+
return value;
|
|
1857
|
+
}
|
|
1858
|
+
const schema = TABLE_SCHEMAS[tableName];
|
|
1859
|
+
const columnSchema = schema?.[columnName];
|
|
1860
|
+
if (columnSchema?.type === "boolean") {
|
|
1861
|
+
return value ? 1 : 0;
|
|
1862
|
+
}
|
|
1863
|
+
if (columnSchema?.type === "jsonb") {
|
|
1864
|
+
if (typeof value === "string") {
|
|
1865
|
+
const trimmed = value.trim();
|
|
1866
|
+
if (trimmed.length > 0) {
|
|
1867
|
+
try {
|
|
1868
|
+
JSON.parse(trimmed);
|
|
1869
|
+
return trimmed;
|
|
1870
|
+
} catch {
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return JSON.stringify(value);
|
|
1461
1874
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORE_BY_ID_FAILED",
|
|
1467
|
-
domain: ErrorDomain.STORAGE,
|
|
1468
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
1469
|
-
details: { id }
|
|
1470
|
-
},
|
|
1471
|
-
error
|
|
1472
|
-
);
|
|
1875
|
+
if (typeof value === "bigint") {
|
|
1876
|
+
return value.toString();
|
|
1877
|
+
}
|
|
1878
|
+
return JSON.stringify(value);
|
|
1473
1879
|
}
|
|
1880
|
+
if (typeof value === "object") {
|
|
1881
|
+
return JSON.stringify(value);
|
|
1882
|
+
}
|
|
1883
|
+
return value;
|
|
1474
1884
|
}
|
|
1475
|
-
|
|
1885
|
+
/**
|
|
1886
|
+
* Maps TABLE_SCHEMAS types to mssql param types (used when value is null)
|
|
1887
|
+
*/
|
|
1888
|
+
getMssqlType(tableName, columnName) {
|
|
1889
|
+
const col = TABLE_SCHEMAS[tableName]?.[columnName];
|
|
1890
|
+
switch (col?.type) {
|
|
1891
|
+
case "text":
|
|
1892
|
+
return sql3.NVarChar;
|
|
1893
|
+
case "timestamp":
|
|
1894
|
+
return sql3.DateTime2;
|
|
1895
|
+
case "uuid":
|
|
1896
|
+
return sql3.UniqueIdentifier;
|
|
1897
|
+
case "jsonb":
|
|
1898
|
+
return sql3.NVarChar;
|
|
1899
|
+
case "integer":
|
|
1900
|
+
return sql3.Int;
|
|
1901
|
+
case "bigint":
|
|
1902
|
+
return sql3.BigInt;
|
|
1903
|
+
case "float":
|
|
1904
|
+
return sql3.Float;
|
|
1905
|
+
case "boolean":
|
|
1906
|
+
return sql3.Bit;
|
|
1907
|
+
default:
|
|
1908
|
+
return sql3.NVarChar;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Update a single record in the database
|
|
1913
|
+
*/
|
|
1914
|
+
async update({
|
|
1915
|
+
tableName,
|
|
1916
|
+
keys,
|
|
1917
|
+
data,
|
|
1918
|
+
transaction
|
|
1919
|
+
}) {
|
|
1920
|
+
try {
|
|
1921
|
+
if (!data || Object.keys(data).length === 0) {
|
|
1922
|
+
throw new MastraError({
|
|
1923
|
+
id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_DATA",
|
|
1924
|
+
domain: ErrorDomain.STORAGE,
|
|
1925
|
+
category: ErrorCategory.USER,
|
|
1926
|
+
text: "Cannot update with empty data payload"
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
if (!keys || Object.keys(keys).length === 0) {
|
|
1930
|
+
throw new MastraError({
|
|
1931
|
+
id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_KEYS",
|
|
1932
|
+
domain: ErrorDomain.STORAGE,
|
|
1933
|
+
category: ErrorCategory.USER,
|
|
1934
|
+
text: "Cannot update without keys to identify records"
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
const setClauses = [];
|
|
1938
|
+
const request = transaction ? transaction.request() : this.pool.request();
|
|
1939
|
+
let paramIndex = 0;
|
|
1940
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
1941
|
+
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
1942
|
+
const paramName = `set${paramIndex++}`;
|
|
1943
|
+
setClauses.push(`[${parsedKey}] = @${paramName}`);
|
|
1944
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1945
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1946
|
+
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1947
|
+
} else {
|
|
1948
|
+
request.input(paramName, preparedValue);
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
const whereConditions = [];
|
|
1952
|
+
Object.entries(keys).forEach(([key, value]) => {
|
|
1953
|
+
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
1954
|
+
const paramName = `where${paramIndex++}`;
|
|
1955
|
+
whereConditions.push(`[${parsedKey}] = @${paramName}`);
|
|
1956
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1957
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1958
|
+
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1959
|
+
} else {
|
|
1960
|
+
request.input(paramName, preparedValue);
|
|
1961
|
+
}
|
|
1962
|
+
});
|
|
1963
|
+
const tableName_ = getTableName({
|
|
1964
|
+
indexName: tableName,
|
|
1965
|
+
schemaName: getSchemaName(this.schemaName)
|
|
1966
|
+
});
|
|
1967
|
+
const updateSql = `UPDATE ${tableName_} SET ${setClauses.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
|
|
1968
|
+
await request.query(updateSql);
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
throw new MastraError(
|
|
1971
|
+
{
|
|
1972
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_FAILED",
|
|
1973
|
+
domain: ErrorDomain.STORAGE,
|
|
1974
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1975
|
+
details: {
|
|
1976
|
+
tableName
|
|
1977
|
+
}
|
|
1978
|
+
},
|
|
1979
|
+
error
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Update multiple records in a single batch transaction
|
|
1985
|
+
*/
|
|
1986
|
+
async batchUpdate({
|
|
1987
|
+
tableName,
|
|
1988
|
+
updates
|
|
1989
|
+
}) {
|
|
1990
|
+
const transaction = this.pool.transaction();
|
|
1991
|
+
try {
|
|
1992
|
+
await transaction.begin();
|
|
1993
|
+
for (const { keys, data } of updates) {
|
|
1994
|
+
await this.update({ tableName, keys, data, transaction });
|
|
1995
|
+
}
|
|
1996
|
+
await transaction.commit();
|
|
1997
|
+
} catch (error) {
|
|
1998
|
+
await transaction.rollback();
|
|
1999
|
+
throw new MastraError(
|
|
2000
|
+
{
|
|
2001
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_UPDATE_FAILED",
|
|
2002
|
+
domain: ErrorDomain.STORAGE,
|
|
2003
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2004
|
+
details: {
|
|
2005
|
+
tableName,
|
|
2006
|
+
numberOfRecords: updates.length
|
|
2007
|
+
}
|
|
2008
|
+
},
|
|
2009
|
+
error
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Delete multiple records by keys
|
|
2015
|
+
*/
|
|
2016
|
+
async batchDelete({ tableName, keys }) {
|
|
2017
|
+
if (keys.length === 0) {
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
const tableName_ = getTableName({
|
|
2021
|
+
indexName: tableName,
|
|
2022
|
+
schemaName: getSchemaName(this.schemaName)
|
|
2023
|
+
});
|
|
2024
|
+
const transaction = this.pool.transaction();
|
|
2025
|
+
try {
|
|
2026
|
+
await transaction.begin();
|
|
2027
|
+
for (const keySet of keys) {
|
|
2028
|
+
const conditions = [];
|
|
2029
|
+
const request = transaction.request();
|
|
2030
|
+
let paramIndex = 0;
|
|
2031
|
+
Object.entries(keySet).forEach(([key, value]) => {
|
|
2032
|
+
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
2033
|
+
const paramName = `p${paramIndex++}`;
|
|
2034
|
+
conditions.push(`[${parsedKey}] = @${paramName}`);
|
|
2035
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
2036
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
2037
|
+
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
2038
|
+
} else {
|
|
2039
|
+
request.input(paramName, preparedValue);
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
const deleteSql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
|
|
2043
|
+
await request.query(deleteSql);
|
|
2044
|
+
}
|
|
2045
|
+
await transaction.commit();
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
await transaction.rollback();
|
|
2048
|
+
throw new MastraError(
|
|
2049
|
+
{
|
|
2050
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_DELETE_FAILED",
|
|
2051
|
+
domain: ErrorDomain.STORAGE,
|
|
2052
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2053
|
+
details: {
|
|
2054
|
+
tableName,
|
|
2055
|
+
numberOfRecords: keys.length
|
|
2056
|
+
}
|
|
2057
|
+
},
|
|
2058
|
+
error
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Create a new index on a table
|
|
2064
|
+
*/
|
|
2065
|
+
async createIndex(options) {
|
|
2066
|
+
try {
|
|
2067
|
+
const { name, table, columns, unique = false, where } = options;
|
|
2068
|
+
const schemaName = this.schemaName || "dbo";
|
|
2069
|
+
const fullTableName = getTableName({
|
|
2070
|
+
indexName: table,
|
|
2071
|
+
schemaName: getSchemaName(this.schemaName)
|
|
2072
|
+
});
|
|
2073
|
+
const indexNameSafe = parseSqlIdentifier(name, "index name");
|
|
2074
|
+
const checkRequest = this.pool.request();
|
|
2075
|
+
checkRequest.input("indexName", indexNameSafe);
|
|
2076
|
+
checkRequest.input("schemaName", schemaName);
|
|
2077
|
+
checkRequest.input("tableName", table);
|
|
2078
|
+
const indexExists = await checkRequest.query(`
|
|
2079
|
+
SELECT 1 as found
|
|
2080
|
+
FROM sys.indexes i
|
|
2081
|
+
INNER JOIN sys.tables t ON i.object_id = t.object_id
|
|
2082
|
+
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
2083
|
+
WHERE i.name = @indexName
|
|
2084
|
+
AND s.name = @schemaName
|
|
2085
|
+
AND t.name = @tableName
|
|
2086
|
+
`);
|
|
2087
|
+
if (indexExists.recordset && indexExists.recordset.length > 0) {
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
const uniqueStr = unique ? "UNIQUE " : "";
|
|
2091
|
+
const columnsStr = columns.map((col) => {
|
|
2092
|
+
if (col.includes(" DESC") || col.includes(" ASC")) {
|
|
2093
|
+
const [colName, ...modifiers] = col.split(" ");
|
|
2094
|
+
if (!colName) {
|
|
2095
|
+
throw new Error(`Invalid column specification: ${col}`);
|
|
2096
|
+
}
|
|
2097
|
+
return `[${parseSqlIdentifier(colName, "column name")}] ${modifiers.join(" ")}`;
|
|
2098
|
+
}
|
|
2099
|
+
return `[${parseSqlIdentifier(col, "column name")}]`;
|
|
2100
|
+
}).join(", ");
|
|
2101
|
+
const whereStr = where ? ` WHERE ${where}` : "";
|
|
2102
|
+
const createIndexSql = `CREATE ${uniqueStr}INDEX [${indexNameSafe}] ON ${fullTableName} (${columnsStr})${whereStr}`;
|
|
2103
|
+
await this.pool.request().query(createIndexSql);
|
|
2104
|
+
} catch (error) {
|
|
2105
|
+
throw new MastraError(
|
|
2106
|
+
{
|
|
2107
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_CREATE_FAILED",
|
|
2108
|
+
domain: ErrorDomain.STORAGE,
|
|
2109
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2110
|
+
details: {
|
|
2111
|
+
indexName: options.name,
|
|
2112
|
+
tableName: options.table
|
|
2113
|
+
}
|
|
2114
|
+
},
|
|
2115
|
+
error
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Drop an existing index
|
|
2121
|
+
*/
|
|
2122
|
+
async dropIndex(indexName) {
|
|
2123
|
+
try {
|
|
2124
|
+
const schemaName = this.schemaName || "dbo";
|
|
2125
|
+
const indexNameSafe = parseSqlIdentifier(indexName, "index name");
|
|
2126
|
+
const checkRequest = this.pool.request();
|
|
2127
|
+
checkRequest.input("indexName", indexNameSafe);
|
|
2128
|
+
checkRequest.input("schemaName", schemaName);
|
|
2129
|
+
const result = await checkRequest.query(`
|
|
2130
|
+
SELECT t.name as table_name
|
|
2131
|
+
FROM sys.indexes i
|
|
2132
|
+
INNER JOIN sys.tables t ON i.object_id = t.object_id
|
|
2133
|
+
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
2134
|
+
WHERE i.name = @indexName
|
|
2135
|
+
AND s.name = @schemaName
|
|
2136
|
+
`);
|
|
2137
|
+
if (!result.recordset || result.recordset.length === 0) {
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
if (result.recordset.length > 1) {
|
|
2141
|
+
const tables = result.recordset.map((r) => r.table_name).join(", ");
|
|
2142
|
+
throw new MastraError({
|
|
2143
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_AMBIGUOUS",
|
|
2144
|
+
domain: ErrorDomain.STORAGE,
|
|
2145
|
+
category: ErrorCategory.USER,
|
|
2146
|
+
text: `Index "${indexNameSafe}" exists on multiple tables (${tables}) in schema "${schemaName}". Please drop indexes manually or ensure unique index names.`
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
const tableName = result.recordset[0].table_name;
|
|
2150
|
+
const fullTableName = getTableName({
|
|
2151
|
+
indexName: tableName,
|
|
2152
|
+
schemaName: getSchemaName(this.schemaName)
|
|
2153
|
+
});
|
|
2154
|
+
const dropSql = `DROP INDEX [${indexNameSafe}] ON ${fullTableName}`;
|
|
2155
|
+
await this.pool.request().query(dropSql);
|
|
2156
|
+
} catch (error) {
|
|
2157
|
+
throw new MastraError(
|
|
2158
|
+
{
|
|
2159
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_DROP_FAILED",
|
|
2160
|
+
domain: ErrorDomain.STORAGE,
|
|
2161
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2162
|
+
details: {
|
|
2163
|
+
indexName
|
|
2164
|
+
}
|
|
2165
|
+
},
|
|
2166
|
+
error
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* List indexes for a specific table or all tables
|
|
2172
|
+
*/
|
|
2173
|
+
async listIndexes(tableName) {
|
|
2174
|
+
try {
|
|
2175
|
+
const schemaName = this.schemaName || "dbo";
|
|
2176
|
+
let query;
|
|
2177
|
+
const request = this.pool.request();
|
|
2178
|
+
request.input("schemaName", schemaName);
|
|
2179
|
+
if (tableName) {
|
|
2180
|
+
query = `
|
|
2181
|
+
SELECT
|
|
2182
|
+
i.name as name,
|
|
2183
|
+
o.name as [table],
|
|
2184
|
+
i.is_unique as is_unique,
|
|
2185
|
+
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
|
|
2186
|
+
FROM sys.indexes i
|
|
2187
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2188
|
+
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2189
|
+
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2190
|
+
WHERE sch.name = @schemaName
|
|
2191
|
+
AND o.name = @tableName
|
|
2192
|
+
AND i.name IS NOT NULL
|
|
2193
|
+
GROUP BY i.name, o.name, i.is_unique
|
|
2194
|
+
`;
|
|
2195
|
+
request.input("tableName", tableName);
|
|
2196
|
+
} else {
|
|
2197
|
+
query = `
|
|
2198
|
+
SELECT
|
|
2199
|
+
i.name as name,
|
|
2200
|
+
o.name as [table],
|
|
2201
|
+
i.is_unique as is_unique,
|
|
2202
|
+
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
|
|
2203
|
+
FROM sys.indexes i
|
|
2204
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2205
|
+
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2206
|
+
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2207
|
+
WHERE sch.name = @schemaName
|
|
2208
|
+
AND i.name IS NOT NULL
|
|
2209
|
+
GROUP BY i.name, o.name, i.is_unique
|
|
2210
|
+
`;
|
|
2211
|
+
}
|
|
2212
|
+
const result = await request.query(query);
|
|
2213
|
+
const indexes = [];
|
|
2214
|
+
for (const row of result.recordset) {
|
|
2215
|
+
const colRequest = this.pool.request();
|
|
2216
|
+
colRequest.input("indexName", row.name);
|
|
2217
|
+
colRequest.input("schemaName", schemaName);
|
|
2218
|
+
const colResult = await colRequest.query(`
|
|
2219
|
+
SELECT c.name as column_name
|
|
2220
|
+
FROM sys.indexes i
|
|
2221
|
+
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
2222
|
+
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
2223
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2224
|
+
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
|
2225
|
+
WHERE i.name = @indexName
|
|
2226
|
+
AND s.name = @schemaName
|
|
2227
|
+
ORDER BY ic.key_ordinal
|
|
2228
|
+
`);
|
|
2229
|
+
indexes.push({
|
|
2230
|
+
name: row.name,
|
|
2231
|
+
table: row.table,
|
|
2232
|
+
columns: colResult.recordset.map((c) => c.column_name),
|
|
2233
|
+
unique: row.is_unique || false,
|
|
2234
|
+
size: row.size || "0 MB",
|
|
2235
|
+
definition: ""
|
|
2236
|
+
// MSSQL doesn't store definition like PG
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
return indexes;
|
|
2240
|
+
} catch (error) {
|
|
2241
|
+
throw new MastraError(
|
|
2242
|
+
{
|
|
2243
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_LIST_FAILED",
|
|
2244
|
+
domain: ErrorDomain.STORAGE,
|
|
2245
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2246
|
+
details: tableName ? {
|
|
2247
|
+
tableName
|
|
2248
|
+
} : {}
|
|
2249
|
+
},
|
|
2250
|
+
error
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Get detailed statistics for a specific index
|
|
2256
|
+
*/
|
|
2257
|
+
async describeIndex(indexName) {
|
|
2258
|
+
try {
|
|
2259
|
+
const schemaName = this.schemaName || "dbo";
|
|
2260
|
+
const request = this.pool.request();
|
|
2261
|
+
request.input("indexName", indexName);
|
|
2262
|
+
request.input("schemaName", schemaName);
|
|
2263
|
+
const query = `
|
|
2264
|
+
SELECT
|
|
2265
|
+
i.name as name,
|
|
2266
|
+
o.name as [table],
|
|
2267
|
+
i.is_unique as is_unique,
|
|
2268
|
+
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size,
|
|
2269
|
+
i.type_desc as method,
|
|
2270
|
+
ISNULL(us.user_scans, 0) as scans,
|
|
2271
|
+
ISNULL(us.user_seeks + us.user_scans, 0) as tuples_read,
|
|
2272
|
+
ISNULL(us.user_lookups, 0) as tuples_fetched
|
|
2273
|
+
FROM sys.indexes i
|
|
2274
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2275
|
+
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2276
|
+
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2277
|
+
LEFT JOIN sys.dm_db_index_usage_stats us ON i.object_id = us.object_id AND i.index_id = us.index_id
|
|
2278
|
+
WHERE i.name = @indexName
|
|
2279
|
+
AND sch.name = @schemaName
|
|
2280
|
+
GROUP BY i.name, o.name, i.is_unique, i.type_desc, us.user_seeks, us.user_scans, us.user_lookups
|
|
2281
|
+
`;
|
|
2282
|
+
const result = await request.query(query);
|
|
2283
|
+
if (!result.recordset || result.recordset.length === 0) {
|
|
2284
|
+
throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
|
|
2285
|
+
}
|
|
2286
|
+
const row = result.recordset[0];
|
|
2287
|
+
const colRequest = this.pool.request();
|
|
2288
|
+
colRequest.input("indexName", indexName);
|
|
2289
|
+
colRequest.input("schemaName", schemaName);
|
|
2290
|
+
const colResult = await colRequest.query(`
|
|
2291
|
+
SELECT c.name as column_name
|
|
2292
|
+
FROM sys.indexes i
|
|
2293
|
+
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
2294
|
+
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
2295
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2296
|
+
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
|
2297
|
+
WHERE i.name = @indexName
|
|
2298
|
+
AND s.name = @schemaName
|
|
2299
|
+
ORDER BY ic.key_ordinal
|
|
2300
|
+
`);
|
|
2301
|
+
return {
|
|
2302
|
+
name: row.name,
|
|
2303
|
+
table: row.table,
|
|
2304
|
+
columns: colResult.recordset.map((c) => c.column_name),
|
|
2305
|
+
unique: row.is_unique || false,
|
|
2306
|
+
size: row.size || "0 MB",
|
|
2307
|
+
definition: "",
|
|
2308
|
+
method: row.method?.toLowerCase() || "nonclustered",
|
|
2309
|
+
scans: Number(row.scans) || 0,
|
|
2310
|
+
tuples_read: Number(row.tuples_read) || 0,
|
|
2311
|
+
tuples_fetched: Number(row.tuples_fetched) || 0
|
|
2312
|
+
};
|
|
2313
|
+
} catch (error) {
|
|
2314
|
+
throw new MastraError(
|
|
2315
|
+
{
|
|
2316
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_DESCRIBE_FAILED",
|
|
2317
|
+
domain: ErrorDomain.STORAGE,
|
|
2318
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2319
|
+
details: {
|
|
2320
|
+
indexName
|
|
2321
|
+
}
|
|
2322
|
+
},
|
|
2323
|
+
error
|
|
2324
|
+
);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Returns definitions for automatic performance indexes
|
|
2329
|
+
* IMPORTANT: Uses seq_id DESC instead of createdAt DESC for MSSQL due to millisecond accuracy limitations
|
|
2330
|
+
* NOTE: Using NVARCHAR(400) for text columns (800 bytes) leaves room for composite indexes
|
|
2331
|
+
*/
|
|
2332
|
+
getAutomaticIndexDefinitions() {
|
|
2333
|
+
const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
|
|
2334
|
+
return [
|
|
2335
|
+
// Composite indexes for optimal filtering + sorting performance
|
|
2336
|
+
// NVARCHAR(400) = 800 bytes, plus BIGINT (8 bytes) = 808 bytes total (under 900-byte limit)
|
|
2337
|
+
{
|
|
2338
|
+
name: `${schemaPrefix}mastra_threads_resourceid_seqid_idx`,
|
|
2339
|
+
table: TABLE_THREADS,
|
|
2340
|
+
columns: ["resourceId", "seq_id DESC"]
|
|
2341
|
+
},
|
|
2342
|
+
{
|
|
2343
|
+
name: `${schemaPrefix}mastra_messages_thread_id_seqid_idx`,
|
|
2344
|
+
table: TABLE_MESSAGES,
|
|
2345
|
+
columns: ["thread_id", "seq_id DESC"]
|
|
2346
|
+
},
|
|
2347
|
+
{
|
|
2348
|
+
name: `${schemaPrefix}mastra_traces_name_seqid_idx`,
|
|
2349
|
+
table: TABLE_TRACES,
|
|
2350
|
+
columns: ["name", "seq_id DESC"]
|
|
2351
|
+
},
|
|
2352
|
+
{
|
|
2353
|
+
name: `${schemaPrefix}mastra_evals_agent_name_seqid_idx`,
|
|
2354
|
+
table: TABLE_EVALS,
|
|
2355
|
+
columns: ["agent_name", "seq_id DESC"]
|
|
2356
|
+
},
|
|
2357
|
+
{
|
|
2358
|
+
name: `${schemaPrefix}mastra_scores_trace_id_span_id_seqid_idx`,
|
|
2359
|
+
table: TABLE_SCORERS,
|
|
2360
|
+
columns: ["traceId", "spanId", "seq_id DESC"]
|
|
2361
|
+
},
|
|
2362
|
+
// AI Spans indexes for optimal trace querying
|
|
2363
|
+
{
|
|
2364
|
+
name: `${schemaPrefix}mastra_ai_spans_traceid_startedat_idx`,
|
|
2365
|
+
table: TABLE_AI_SPANS,
|
|
2366
|
+
columns: ["traceId", "startedAt DESC"]
|
|
2367
|
+
},
|
|
2368
|
+
{
|
|
2369
|
+
name: `${schemaPrefix}mastra_ai_spans_parentspanid_startedat_idx`,
|
|
2370
|
+
table: TABLE_AI_SPANS,
|
|
2371
|
+
columns: ["parentSpanId", "startedAt DESC"]
|
|
2372
|
+
},
|
|
2373
|
+
{
|
|
2374
|
+
name: `${schemaPrefix}mastra_ai_spans_name_idx`,
|
|
2375
|
+
table: TABLE_AI_SPANS,
|
|
2376
|
+
columns: ["name"]
|
|
2377
|
+
},
|
|
2378
|
+
{
|
|
2379
|
+
name: `${schemaPrefix}mastra_ai_spans_spantype_startedat_idx`,
|
|
2380
|
+
table: TABLE_AI_SPANS,
|
|
2381
|
+
columns: ["spanType", "startedAt DESC"]
|
|
2382
|
+
}
|
|
2383
|
+
];
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Creates automatic indexes for optimal query performance
|
|
2387
|
+
* Uses getAutomaticIndexDefinitions() to determine which indexes to create
|
|
2388
|
+
*/
|
|
2389
|
+
async createAutomaticIndexes() {
|
|
2390
|
+
try {
|
|
2391
|
+
const indexes = this.getAutomaticIndexDefinitions();
|
|
2392
|
+
for (const indexOptions of indexes) {
|
|
2393
|
+
try {
|
|
2394
|
+
await this.createIndex(indexOptions);
|
|
2395
|
+
} catch (error) {
|
|
2396
|
+
this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
} catch (error) {
|
|
2400
|
+
throw new MastraError(
|
|
2401
|
+
{
|
|
2402
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
|
|
2403
|
+
domain: ErrorDomain.STORAGE,
|
|
2404
|
+
category: ErrorCategory.THIRD_PARTY
|
|
2405
|
+
},
|
|
2406
|
+
error
|
|
2407
|
+
);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
function transformScoreRow(row) {
|
|
2412
|
+
return {
|
|
2413
|
+
...row,
|
|
2414
|
+
input: safelyParseJSON(row.input),
|
|
2415
|
+
scorer: safelyParseJSON(row.scorer),
|
|
2416
|
+
preprocessStepResult: safelyParseJSON(row.preprocessStepResult),
|
|
2417
|
+
analyzeStepResult: safelyParseJSON(row.analyzeStepResult),
|
|
2418
|
+
metadata: safelyParseJSON(row.metadata),
|
|
2419
|
+
output: safelyParseJSON(row.output),
|
|
2420
|
+
additionalContext: safelyParseJSON(row.additionalContext),
|
|
2421
|
+
runtimeContext: safelyParseJSON(row.runtimeContext),
|
|
2422
|
+
entity: safelyParseJSON(row.entity),
|
|
2423
|
+
createdAt: new Date(row.createdAt),
|
|
2424
|
+
updatedAt: new Date(row.updatedAt)
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
var ScoresMSSQL = class extends ScoresStorage {
|
|
2428
|
+
pool;
|
|
2429
|
+
operations;
|
|
2430
|
+
schema;
|
|
2431
|
+
constructor({
|
|
2432
|
+
pool,
|
|
2433
|
+
operations,
|
|
2434
|
+
schema
|
|
2435
|
+
}) {
|
|
2436
|
+
super();
|
|
2437
|
+
this.pool = pool;
|
|
2438
|
+
this.operations = operations;
|
|
2439
|
+
this.schema = schema;
|
|
2440
|
+
}
|
|
2441
|
+
async getScoreById({ id }) {
|
|
1476
2442
|
try {
|
|
1477
|
-
const
|
|
2443
|
+
const request = this.pool.request();
|
|
2444
|
+
request.input("p1", id);
|
|
2445
|
+
const result = await request.query(
|
|
2446
|
+
`SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE id = @p1`
|
|
2447
|
+
);
|
|
2448
|
+
if (result.recordset.length === 0) {
|
|
2449
|
+
return null;
|
|
2450
|
+
}
|
|
2451
|
+
return transformScoreRow(result.recordset[0]);
|
|
2452
|
+
} catch (error) {
|
|
2453
|
+
throw new MastraError(
|
|
2454
|
+
{
|
|
2455
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORE_BY_ID_FAILED",
|
|
2456
|
+
domain: ErrorDomain.STORAGE,
|
|
2457
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2458
|
+
details: { id }
|
|
2459
|
+
},
|
|
2460
|
+
error
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
async saveScore(score) {
|
|
2465
|
+
let validatedScore;
|
|
2466
|
+
try {
|
|
2467
|
+
validatedScore = saveScorePayloadSchema.parse(score);
|
|
2468
|
+
} catch (error) {
|
|
2469
|
+
throw new MastraError(
|
|
2470
|
+
{
|
|
2471
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_SCORE_VALIDATION_FAILED",
|
|
2472
|
+
domain: ErrorDomain.STORAGE,
|
|
2473
|
+
category: ErrorCategory.THIRD_PARTY
|
|
2474
|
+
},
|
|
2475
|
+
error
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
try {
|
|
2479
|
+
const scoreId = randomUUID();
|
|
1478
2480
|
const {
|
|
1479
2481
|
scorer,
|
|
1480
2482
|
preprocessStepResult,
|
|
@@ -1486,21 +2488,21 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1486
2488
|
runtimeContext,
|
|
1487
2489
|
entity,
|
|
1488
2490
|
...rest
|
|
1489
|
-
} =
|
|
2491
|
+
} = validatedScore;
|
|
1490
2492
|
await this.operations.insert({
|
|
1491
2493
|
tableName: TABLE_SCORERS,
|
|
1492
2494
|
record: {
|
|
1493
2495
|
id: scoreId,
|
|
1494
2496
|
...rest,
|
|
1495
|
-
input:
|
|
1496
|
-
output:
|
|
1497
|
-
preprocessStepResult: preprocessStepResult
|
|
1498
|
-
analyzeStepResult: analyzeStepResult
|
|
1499
|
-
metadata: metadata
|
|
1500
|
-
additionalContext: additionalContext
|
|
1501
|
-
runtimeContext: runtimeContext
|
|
1502
|
-
entity: entity
|
|
1503
|
-
scorer: scorer
|
|
2497
|
+
input: input || "",
|
|
2498
|
+
output: output || "",
|
|
2499
|
+
preprocessStepResult: preprocessStepResult || null,
|
|
2500
|
+
analyzeStepResult: analyzeStepResult || null,
|
|
2501
|
+
metadata: metadata || null,
|
|
2502
|
+
additionalContext: additionalContext || null,
|
|
2503
|
+
runtimeContext: runtimeContext || null,
|
|
2504
|
+
entity: entity || null,
|
|
2505
|
+
scorer: scorer || null,
|
|
1504
2506
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1505
2507
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1506
2508
|
}
|
|
@@ -1520,14 +2522,37 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1520
2522
|
}
|
|
1521
2523
|
async getScoresByScorerId({
|
|
1522
2524
|
scorerId,
|
|
1523
|
-
pagination
|
|
2525
|
+
pagination,
|
|
2526
|
+
entityId,
|
|
2527
|
+
entityType,
|
|
2528
|
+
source
|
|
1524
2529
|
}) {
|
|
1525
2530
|
try {
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
2531
|
+
const conditions = ["[scorerId] = @p1"];
|
|
2532
|
+
const params = { p1: scorerId };
|
|
2533
|
+
let paramIndex = 2;
|
|
2534
|
+
if (entityId) {
|
|
2535
|
+
conditions.push(`[entityId] = @p${paramIndex}`);
|
|
2536
|
+
params[`p${paramIndex}`] = entityId;
|
|
2537
|
+
paramIndex++;
|
|
2538
|
+
}
|
|
2539
|
+
if (entityType) {
|
|
2540
|
+
conditions.push(`[entityType] = @p${paramIndex}`);
|
|
2541
|
+
params[`p${paramIndex}`] = entityType;
|
|
2542
|
+
paramIndex++;
|
|
2543
|
+
}
|
|
2544
|
+
if (source) {
|
|
2545
|
+
conditions.push(`[source] = @p${paramIndex}`);
|
|
2546
|
+
params[`p${paramIndex}`] = source;
|
|
2547
|
+
paramIndex++;
|
|
2548
|
+
}
|
|
2549
|
+
const whereClause = conditions.join(" AND ");
|
|
2550
|
+
const tableName = getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) });
|
|
2551
|
+
const countRequest = this.pool.request();
|
|
2552
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
2553
|
+
countRequest.input(key, value);
|
|
2554
|
+
});
|
|
2555
|
+
const totalResult = await countRequest.query(`SELECT COUNT(*) as count FROM ${tableName} WHERE ${whereClause}`);
|
|
1531
2556
|
const total = totalResult.recordset[0]?.count || 0;
|
|
1532
2557
|
if (total === 0) {
|
|
1533
2558
|
return {
|
|
@@ -1541,12 +2566,13 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1541
2566
|
};
|
|
1542
2567
|
}
|
|
1543
2568
|
const dataRequest = this.pool.request();
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
2569
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
2570
|
+
dataRequest.input(key, value);
|
|
2571
|
+
});
|
|
2572
|
+
dataRequest.input("perPage", pagination.perPage);
|
|
2573
|
+
dataRequest.input("offset", pagination.page * pagination.perPage);
|
|
2574
|
+
const dataQuery = `SELECT * FROM ${tableName} WHERE ${whereClause} ORDER BY [createdAt] DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
2575
|
+
const result = await dataRequest.query(dataQuery);
|
|
1550
2576
|
return {
|
|
1551
2577
|
pagination: {
|
|
1552
2578
|
total: Number(total),
|
|
@@ -1671,6 +2697,60 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1671
2697
|
);
|
|
1672
2698
|
}
|
|
1673
2699
|
}
|
|
2700
|
+
async getScoresBySpan({
|
|
2701
|
+
traceId,
|
|
2702
|
+
spanId,
|
|
2703
|
+
pagination
|
|
2704
|
+
}) {
|
|
2705
|
+
try {
|
|
2706
|
+
const request = this.pool.request();
|
|
2707
|
+
request.input("p1", traceId);
|
|
2708
|
+
request.input("p2", spanId);
|
|
2709
|
+
const totalResult = await request.query(
|
|
2710
|
+
`SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2`
|
|
2711
|
+
);
|
|
2712
|
+
const total = totalResult.recordset[0]?.count || 0;
|
|
2713
|
+
if (total === 0) {
|
|
2714
|
+
return {
|
|
2715
|
+
pagination: {
|
|
2716
|
+
total: 0,
|
|
2717
|
+
page: pagination.page,
|
|
2718
|
+
perPage: pagination.perPage,
|
|
2719
|
+
hasMore: false
|
|
2720
|
+
},
|
|
2721
|
+
scores: []
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
const limit = pagination.perPage + 1;
|
|
2725
|
+
const dataRequest = this.pool.request();
|
|
2726
|
+
dataRequest.input("p1", traceId);
|
|
2727
|
+
dataRequest.input("p2", spanId);
|
|
2728
|
+
dataRequest.input("p3", limit);
|
|
2729
|
+
dataRequest.input("p4", pagination.page * pagination.perPage);
|
|
2730
|
+
const result = await dataRequest.query(
|
|
2731
|
+
`SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
|
|
2732
|
+
);
|
|
2733
|
+
return {
|
|
2734
|
+
pagination: {
|
|
2735
|
+
total: Number(total),
|
|
2736
|
+
page: pagination.page,
|
|
2737
|
+
perPage: pagination.perPage,
|
|
2738
|
+
hasMore: result.recordset.length > pagination.perPage
|
|
2739
|
+
},
|
|
2740
|
+
scores: result.recordset.slice(0, pagination.perPage).map((row) => transformScoreRow(row))
|
|
2741
|
+
};
|
|
2742
|
+
} catch (error) {
|
|
2743
|
+
throw new MastraError(
|
|
2744
|
+
{
|
|
2745
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORES_BY_SPAN_FAILED",
|
|
2746
|
+
domain: ErrorDomain.STORAGE,
|
|
2747
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2748
|
+
details: { traceId, spanId }
|
|
2749
|
+
},
|
|
2750
|
+
error
|
|
2751
|
+
);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
1674
2754
|
};
|
|
1675
2755
|
var TracesMSSQL = class extends TracesStorage {
|
|
1676
2756
|
pool;
|
|
@@ -1749,7 +2829,7 @@ var TracesMSSQL = class extends TracesStorage {
|
|
|
1749
2829
|
const countRequest = this.pool.request();
|
|
1750
2830
|
Object.entries(paramMap).forEach(([key, value]) => {
|
|
1751
2831
|
if (value instanceof Date) {
|
|
1752
|
-
countRequest.input(key,
|
|
2832
|
+
countRequest.input(key, sql3.DateTime, value);
|
|
1753
2833
|
} else {
|
|
1754
2834
|
countRequest.input(key, value);
|
|
1755
2835
|
}
|
|
@@ -1783,7 +2863,7 @@ var TracesMSSQL = class extends TracesStorage {
|
|
|
1783
2863
|
const dataRequest = this.pool.request();
|
|
1784
2864
|
Object.entries(paramMap).forEach(([key, value]) => {
|
|
1785
2865
|
if (value instanceof Date) {
|
|
1786
|
-
dataRequest.input(key,
|
|
2866
|
+
dataRequest.input(key, sql3.DateTime, value);
|
|
1787
2867
|
} else {
|
|
1788
2868
|
dataRequest.input(key, value);
|
|
1789
2869
|
}
|
|
@@ -1839,24 +2919,6 @@ var TracesMSSQL = class extends TracesStorage {
|
|
|
1839
2919
|
});
|
|
1840
2920
|
}
|
|
1841
2921
|
};
|
|
1842
|
-
function parseWorkflowRun(row) {
|
|
1843
|
-
let parsedSnapshot = row.snapshot;
|
|
1844
|
-
if (typeof parsedSnapshot === "string") {
|
|
1845
|
-
try {
|
|
1846
|
-
parsedSnapshot = JSON.parse(row.snapshot);
|
|
1847
|
-
} catch (e) {
|
|
1848
|
-
console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
return {
|
|
1852
|
-
workflowName: row.workflow_name,
|
|
1853
|
-
runId: row.run_id,
|
|
1854
|
-
snapshot: parsedSnapshot,
|
|
1855
|
-
createdAt: row.createdAt,
|
|
1856
|
-
updatedAt: row.updatedAt,
|
|
1857
|
-
resourceId: row.resourceId
|
|
1858
|
-
};
|
|
1859
|
-
}
|
|
1860
2922
|
var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
1861
2923
|
pool;
|
|
1862
2924
|
operations;
|
|
@@ -1871,21 +2933,164 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1871
2933
|
this.operations = operations;
|
|
1872
2934
|
this.schema = schema;
|
|
1873
2935
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2936
|
+
parseWorkflowRun(row) {
|
|
2937
|
+
let parsedSnapshot = row.snapshot;
|
|
2938
|
+
if (typeof parsedSnapshot === "string") {
|
|
2939
|
+
try {
|
|
2940
|
+
parsedSnapshot = JSON.parse(row.snapshot);
|
|
2941
|
+
} catch (e) {
|
|
2942
|
+
this.logger?.warn?.(`Failed to parse snapshot for workflow ${row.workflow_name}:`, e);
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
return {
|
|
2946
|
+
workflowName: row.workflow_name,
|
|
2947
|
+
runId: row.run_id,
|
|
2948
|
+
snapshot: parsedSnapshot,
|
|
2949
|
+
createdAt: row.createdAt,
|
|
2950
|
+
updatedAt: row.updatedAt,
|
|
2951
|
+
resourceId: row.resourceId
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
async updateWorkflowResults({
|
|
2955
|
+
workflowName,
|
|
2956
|
+
runId,
|
|
2957
|
+
stepId,
|
|
2958
|
+
result,
|
|
2959
|
+
runtimeContext
|
|
1880
2960
|
}) {
|
|
1881
|
-
|
|
2961
|
+
const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
2962
|
+
const transaction = this.pool.transaction();
|
|
2963
|
+
try {
|
|
2964
|
+
await transaction.begin();
|
|
2965
|
+
const selectRequest = new sql3.Request(transaction);
|
|
2966
|
+
selectRequest.input("workflow_name", workflowName);
|
|
2967
|
+
selectRequest.input("run_id", runId);
|
|
2968
|
+
const existingSnapshotResult = await selectRequest.query(
|
|
2969
|
+
`SELECT snapshot FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
2970
|
+
);
|
|
2971
|
+
let snapshot;
|
|
2972
|
+
if (!existingSnapshotResult.recordset || existingSnapshotResult.recordset.length === 0) {
|
|
2973
|
+
snapshot = {
|
|
2974
|
+
context: {},
|
|
2975
|
+
activePaths: [],
|
|
2976
|
+
activeStepsPath: {},
|
|
2977
|
+
timestamp: Date.now(),
|
|
2978
|
+
suspendedPaths: {},
|
|
2979
|
+
resumeLabels: {},
|
|
2980
|
+
serializedStepGraph: [],
|
|
2981
|
+
status: "pending",
|
|
2982
|
+
value: {},
|
|
2983
|
+
waitingPaths: {},
|
|
2984
|
+
runId,
|
|
2985
|
+
runtimeContext: {}
|
|
2986
|
+
};
|
|
2987
|
+
} else {
|
|
2988
|
+
const existingSnapshot = existingSnapshotResult.recordset[0].snapshot;
|
|
2989
|
+
snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
|
|
2990
|
+
}
|
|
2991
|
+
snapshot.context[stepId] = result;
|
|
2992
|
+
snapshot.runtimeContext = { ...snapshot.runtimeContext, ...runtimeContext };
|
|
2993
|
+
const upsertReq = new sql3.Request(transaction);
|
|
2994
|
+
upsertReq.input("workflow_name", workflowName);
|
|
2995
|
+
upsertReq.input("run_id", runId);
|
|
2996
|
+
upsertReq.input("snapshot", JSON.stringify(snapshot));
|
|
2997
|
+
upsertReq.input("createdAt", sql3.DateTime2, /* @__PURE__ */ new Date());
|
|
2998
|
+
upsertReq.input("updatedAt", sql3.DateTime2, /* @__PURE__ */ new Date());
|
|
2999
|
+
await upsertReq.query(
|
|
3000
|
+
`MERGE ${table} AS target
|
|
3001
|
+
USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
|
|
3002
|
+
ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
|
|
3003
|
+
WHEN MATCHED THEN UPDATE SET snapshot = @snapshot, [updatedAt] = @updatedAt
|
|
3004
|
+
WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, snapshot, [createdAt], [updatedAt])
|
|
3005
|
+
VALUES (@workflow_name, @run_id, @snapshot, @createdAt, @updatedAt);`
|
|
3006
|
+
);
|
|
3007
|
+
await transaction.commit();
|
|
3008
|
+
return snapshot.context;
|
|
3009
|
+
} catch (error) {
|
|
3010
|
+
try {
|
|
3011
|
+
await transaction.rollback();
|
|
3012
|
+
} catch {
|
|
3013
|
+
}
|
|
3014
|
+
throw new MastraError(
|
|
3015
|
+
{
|
|
3016
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_RESULTS_FAILED",
|
|
3017
|
+
domain: ErrorDomain.STORAGE,
|
|
3018
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3019
|
+
details: {
|
|
3020
|
+
workflowName,
|
|
3021
|
+
runId,
|
|
3022
|
+
stepId
|
|
3023
|
+
}
|
|
3024
|
+
},
|
|
3025
|
+
error
|
|
3026
|
+
);
|
|
3027
|
+
}
|
|
1882
3028
|
}
|
|
1883
|
-
updateWorkflowState({
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
3029
|
+
async updateWorkflowState({
|
|
3030
|
+
workflowName,
|
|
3031
|
+
runId,
|
|
3032
|
+
opts
|
|
1887
3033
|
}) {
|
|
1888
|
-
|
|
3034
|
+
const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
3035
|
+
const transaction = this.pool.transaction();
|
|
3036
|
+
try {
|
|
3037
|
+
await transaction.begin();
|
|
3038
|
+
const selectRequest = new sql3.Request(transaction);
|
|
3039
|
+
selectRequest.input("workflow_name", workflowName);
|
|
3040
|
+
selectRequest.input("run_id", runId);
|
|
3041
|
+
const existingSnapshotResult = await selectRequest.query(
|
|
3042
|
+
`SELECT snapshot FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
3043
|
+
);
|
|
3044
|
+
if (!existingSnapshotResult.recordset || existingSnapshotResult.recordset.length === 0) {
|
|
3045
|
+
await transaction.rollback();
|
|
3046
|
+
return void 0;
|
|
3047
|
+
}
|
|
3048
|
+
const existingSnapshot = existingSnapshotResult.recordset[0].snapshot;
|
|
3049
|
+
const snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
|
|
3050
|
+
if (!snapshot || !snapshot?.context) {
|
|
3051
|
+
await transaction.rollback();
|
|
3052
|
+
throw new MastraError(
|
|
3053
|
+
{
|
|
3054
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_SNAPSHOT_NOT_FOUND",
|
|
3055
|
+
domain: ErrorDomain.STORAGE,
|
|
3056
|
+
category: ErrorCategory.SYSTEM,
|
|
3057
|
+
details: {
|
|
3058
|
+
workflowName,
|
|
3059
|
+
runId
|
|
3060
|
+
}
|
|
3061
|
+
},
|
|
3062
|
+
new Error(`Snapshot not found for runId ${runId}`)
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
const updatedSnapshot = { ...snapshot, ...opts };
|
|
3066
|
+
const updateRequest = new sql3.Request(transaction);
|
|
3067
|
+
updateRequest.input("snapshot", JSON.stringify(updatedSnapshot));
|
|
3068
|
+
updateRequest.input("workflow_name", workflowName);
|
|
3069
|
+
updateRequest.input("run_id", runId);
|
|
3070
|
+
updateRequest.input("updatedAt", sql3.DateTime2, /* @__PURE__ */ new Date());
|
|
3071
|
+
await updateRequest.query(
|
|
3072
|
+
`UPDATE ${table} SET snapshot = @snapshot, [updatedAt] = @updatedAt WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
3073
|
+
);
|
|
3074
|
+
await transaction.commit();
|
|
3075
|
+
return updatedSnapshot;
|
|
3076
|
+
} catch (error) {
|
|
3077
|
+
try {
|
|
3078
|
+
await transaction.rollback();
|
|
3079
|
+
} catch {
|
|
3080
|
+
}
|
|
3081
|
+
throw new MastraError(
|
|
3082
|
+
{
|
|
3083
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_FAILED",
|
|
3084
|
+
domain: ErrorDomain.STORAGE,
|
|
3085
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
3086
|
+
details: {
|
|
3087
|
+
workflowName,
|
|
3088
|
+
runId
|
|
3089
|
+
}
|
|
3090
|
+
},
|
|
3091
|
+
error
|
|
3092
|
+
);
|
|
3093
|
+
}
|
|
1889
3094
|
}
|
|
1890
3095
|
async persistWorkflowSnapshot({
|
|
1891
3096
|
workflowName,
|
|
@@ -1901,8 +3106,8 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1901
3106
|
request.input("run_id", runId);
|
|
1902
3107
|
request.input("resourceId", resourceId);
|
|
1903
3108
|
request.input("snapshot", JSON.stringify(snapshot));
|
|
1904
|
-
request.input("createdAt",
|
|
1905
|
-
request.input("updatedAt",
|
|
3109
|
+
request.input("createdAt", sql3.DateTime2, new Date(now));
|
|
3110
|
+
request.input("updatedAt", sql3.DateTime2, new Date(now));
|
|
1906
3111
|
const mergeSql = `MERGE INTO ${table} AS target
|
|
1907
3112
|
USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
|
|
1908
3113
|
ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
|
|
@@ -1983,7 +3188,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1983
3188
|
if (!result.recordset || result.recordset.length === 0) {
|
|
1984
3189
|
return null;
|
|
1985
3190
|
}
|
|
1986
|
-
return parseWorkflowRun(result.recordset[0]);
|
|
3191
|
+
return this.parseWorkflowRun(result.recordset[0]);
|
|
1987
3192
|
} catch (error) {
|
|
1988
3193
|
throw new MastraError(
|
|
1989
3194
|
{
|
|
@@ -2005,7 +3210,8 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2005
3210
|
toDate,
|
|
2006
3211
|
limit,
|
|
2007
3212
|
offset,
|
|
2008
|
-
resourceId
|
|
3213
|
+
resourceId,
|
|
3214
|
+
status
|
|
2009
3215
|
} = {}) {
|
|
2010
3216
|
try {
|
|
2011
3217
|
const conditions = [];
|
|
@@ -2014,13 +3220,17 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2014
3220
|
conditions.push(`[workflow_name] = @workflowName`);
|
|
2015
3221
|
paramMap["workflowName"] = workflowName;
|
|
2016
3222
|
}
|
|
3223
|
+
if (status) {
|
|
3224
|
+
conditions.push(`JSON_VALUE([snapshot], '$.status') = @status`);
|
|
3225
|
+
paramMap["status"] = status;
|
|
3226
|
+
}
|
|
2017
3227
|
if (resourceId) {
|
|
2018
3228
|
const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
|
|
2019
3229
|
if (hasResourceId) {
|
|
2020
3230
|
conditions.push(`[resourceId] = @resourceId`);
|
|
2021
3231
|
paramMap["resourceId"] = resourceId;
|
|
2022
3232
|
} else {
|
|
2023
|
-
|
|
3233
|
+
this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
2024
3234
|
}
|
|
2025
3235
|
}
|
|
2026
3236
|
if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
|
|
@@ -2037,7 +3247,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2037
3247
|
const request = this.pool.request();
|
|
2038
3248
|
Object.entries(paramMap).forEach(([key, value]) => {
|
|
2039
3249
|
if (value instanceof Date) {
|
|
2040
|
-
request.input(key,
|
|
3250
|
+
request.input(key, sql3.DateTime, value);
|
|
2041
3251
|
} else {
|
|
2042
3252
|
request.input(key, value);
|
|
2043
3253
|
}
|
|
@@ -2054,7 +3264,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2054
3264
|
request.input("offset", offset);
|
|
2055
3265
|
}
|
|
2056
3266
|
const result = await request.query(query);
|
|
2057
|
-
const runs = (result.recordset || []).map((row) => parseWorkflowRun(row));
|
|
3267
|
+
const runs = (result.recordset || []).map((row) => this.parseWorkflowRun(row));
|
|
2058
3268
|
return { runs, total: total || runs.length };
|
|
2059
3269
|
} catch (error) {
|
|
2060
3270
|
throw new MastraError(
|
|
@@ -2094,7 +3304,7 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2094
3304
|
}
|
|
2095
3305
|
}
|
|
2096
3306
|
this.schema = config.schemaName || "dbo";
|
|
2097
|
-
this.pool = "connectionString" in config ? new
|
|
3307
|
+
this.pool = "connectionString" in config ? new sql3.ConnectionPool(config.connectionString) : new sql3.ConnectionPool({
|
|
2098
3308
|
server: config.server,
|
|
2099
3309
|
database: config.database,
|
|
2100
3310
|
user: config.user,
|
|
@@ -2108,13 +3318,15 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2108
3318
|
const traces = new TracesMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2109
3319
|
const workflows = new WorkflowsMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2110
3320
|
const memory = new MemoryMSSQL({ pool: this.pool, schema: this.schema, operations });
|
|
3321
|
+
const observability = new ObservabilityMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2111
3322
|
this.stores = {
|
|
2112
3323
|
operations,
|
|
2113
3324
|
scores,
|
|
2114
3325
|
traces,
|
|
2115
3326
|
workflows,
|
|
2116
3327
|
legacyEvals,
|
|
2117
|
-
memory
|
|
3328
|
+
memory,
|
|
3329
|
+
observability
|
|
2118
3330
|
};
|
|
2119
3331
|
} catch (e) {
|
|
2120
3332
|
throw new MastraError(
|
|
@@ -2134,6 +3346,11 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2134
3346
|
try {
|
|
2135
3347
|
await this.isConnected;
|
|
2136
3348
|
await super.init();
|
|
3349
|
+
try {
|
|
3350
|
+
await this.stores.operations.createAutomaticIndexes();
|
|
3351
|
+
} catch (indexError) {
|
|
3352
|
+
this.logger?.warn?.("Failed to create indexes:", indexError);
|
|
3353
|
+
}
|
|
2137
3354
|
} catch (error) {
|
|
2138
3355
|
this.isConnected = null;
|
|
2139
3356
|
throw new MastraError(
|
|
@@ -2160,7 +3377,10 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2160
3377
|
resourceWorkingMemory: true,
|
|
2161
3378
|
hasColumn: true,
|
|
2162
3379
|
createTable: true,
|
|
2163
|
-
deleteMessages: true
|
|
3380
|
+
deleteMessages: true,
|
|
3381
|
+
getScoresBySpan: true,
|
|
3382
|
+
aiTracing: true,
|
|
3383
|
+
indexManagement: true
|
|
2164
3384
|
};
|
|
2165
3385
|
}
|
|
2166
3386
|
/** @deprecated use getEvals instead */
|
|
@@ -2307,15 +3527,8 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2307
3527
|
}) {
|
|
2308
3528
|
return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
|
|
2309
3529
|
}
|
|
2310
|
-
async getWorkflowRuns({
|
|
2311
|
-
|
|
2312
|
-
fromDate,
|
|
2313
|
-
toDate,
|
|
2314
|
-
limit,
|
|
2315
|
-
offset,
|
|
2316
|
-
resourceId
|
|
2317
|
-
} = {}) {
|
|
2318
|
-
return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
|
|
3530
|
+
async getWorkflowRuns(args = {}) {
|
|
3531
|
+
return this.stores.workflows.getWorkflowRuns(args);
|
|
2319
3532
|
}
|
|
2320
3533
|
async getWorkflowRunById({
|
|
2321
3534
|
runId,
|
|
@@ -2326,6 +3539,60 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2326
3539
|
async close() {
|
|
2327
3540
|
await this.pool.close();
|
|
2328
3541
|
}
|
|
3542
|
+
/**
|
|
3543
|
+
* Index Management
|
|
3544
|
+
*/
|
|
3545
|
+
async createIndex(options) {
|
|
3546
|
+
return this.stores.operations.createIndex(options);
|
|
3547
|
+
}
|
|
3548
|
+
async listIndexes(tableName) {
|
|
3549
|
+
return this.stores.operations.listIndexes(tableName);
|
|
3550
|
+
}
|
|
3551
|
+
async describeIndex(indexName) {
|
|
3552
|
+
return this.stores.operations.describeIndex(indexName);
|
|
3553
|
+
}
|
|
3554
|
+
async dropIndex(indexName) {
|
|
3555
|
+
return this.stores.operations.dropIndex(indexName);
|
|
3556
|
+
}
|
|
3557
|
+
/**
|
|
3558
|
+
* AI Tracing / Observability
|
|
3559
|
+
*/
|
|
3560
|
+
getObservabilityStore() {
|
|
3561
|
+
if (!this.stores.observability) {
|
|
3562
|
+
throw new MastraError({
|
|
3563
|
+
id: "MSSQL_STORE_OBSERVABILITY_NOT_INITIALIZED",
|
|
3564
|
+
domain: ErrorDomain.STORAGE,
|
|
3565
|
+
category: ErrorCategory.SYSTEM,
|
|
3566
|
+
text: "Observability storage is not initialized"
|
|
3567
|
+
});
|
|
3568
|
+
}
|
|
3569
|
+
return this.stores.observability;
|
|
3570
|
+
}
|
|
3571
|
+
async createAISpan(span) {
|
|
3572
|
+
return this.getObservabilityStore().createAISpan(span);
|
|
3573
|
+
}
|
|
3574
|
+
async updateAISpan({
|
|
3575
|
+
spanId,
|
|
3576
|
+
traceId,
|
|
3577
|
+
updates
|
|
3578
|
+
}) {
|
|
3579
|
+
return this.getObservabilityStore().updateAISpan({ spanId, traceId, updates });
|
|
3580
|
+
}
|
|
3581
|
+
async getAITrace(traceId) {
|
|
3582
|
+
return this.getObservabilityStore().getAITrace(traceId);
|
|
3583
|
+
}
|
|
3584
|
+
async getAITracesPaginated(args) {
|
|
3585
|
+
return this.getObservabilityStore().getAITracesPaginated(args);
|
|
3586
|
+
}
|
|
3587
|
+
async batchCreateAISpans(args) {
|
|
3588
|
+
return this.getObservabilityStore().batchCreateAISpans(args);
|
|
3589
|
+
}
|
|
3590
|
+
async batchUpdateAISpans(args) {
|
|
3591
|
+
return this.getObservabilityStore().batchUpdateAISpans(args);
|
|
3592
|
+
}
|
|
3593
|
+
async batchDeleteAITraces(args) {
|
|
3594
|
+
return this.getObservabilityStore().batchDeleteAITraces(args);
|
|
3595
|
+
}
|
|
2329
3596
|
/**
|
|
2330
3597
|
* Scorers
|
|
2331
3598
|
*/
|
|
@@ -2334,9 +3601,18 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2334
3601
|
}
|
|
2335
3602
|
async getScoresByScorerId({
|
|
2336
3603
|
scorerId: _scorerId,
|
|
2337
|
-
pagination: _pagination
|
|
3604
|
+
pagination: _pagination,
|
|
3605
|
+
entityId: _entityId,
|
|
3606
|
+
entityType: _entityType,
|
|
3607
|
+
source: _source
|
|
2338
3608
|
}) {
|
|
2339
|
-
return this.stores.scores.getScoresByScorerId({
|
|
3609
|
+
return this.stores.scores.getScoresByScorerId({
|
|
3610
|
+
scorerId: _scorerId,
|
|
3611
|
+
pagination: _pagination,
|
|
3612
|
+
entityId: _entityId,
|
|
3613
|
+
entityType: _entityType,
|
|
3614
|
+
source: _source
|
|
3615
|
+
});
|
|
2340
3616
|
}
|
|
2341
3617
|
async saveScore(_score) {
|
|
2342
3618
|
return this.stores.scores.saveScore(_score);
|
|
@@ -2358,6 +3634,13 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2358
3634
|
pagination: _pagination
|
|
2359
3635
|
});
|
|
2360
3636
|
}
|
|
3637
|
+
async getScoresBySpan({
|
|
3638
|
+
traceId,
|
|
3639
|
+
spanId,
|
|
3640
|
+
pagination: _pagination
|
|
3641
|
+
}) {
|
|
3642
|
+
return this.stores.scores.getScoresBySpan({ traceId, spanId, pagination: _pagination });
|
|
3643
|
+
}
|
|
2361
3644
|
};
|
|
2362
3645
|
|
|
2363
3646
|
export { MSSQLStore };
|