@mastra/mssql 0.0.0-fix-memory-search-fetch-20251027160505 → 0.0.0-fix-persist-session-cache-option-mcp-server-20251030161352
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 +32 -3
- package/README.md +315 -36
- package/dist/index.cjs +1318 -268
- 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 +1320 -270
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +6 -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 +3 -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 +6 -3
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +45 -13
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/storage/domains/legacy-evals/index.d.ts +0 -20
- package/dist/storage/domains/legacy-evals/index.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
2
|
-
import { MastraStorage,
|
|
2
|
+
import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_TRACES, TABLE_SCORERS, TABLE_AI_SPANS, ScoresStorage, WorkflowsStorage, MemoryStorage, resolveMessageLimit, TABLE_RESOURCES, ObservabilityStorage, safelyParseJSON } from '@mastra/core/storage';
|
|
3
3
|
import sql2 from 'mssql';
|
|
4
|
-
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
5
4
|
import { MessageList } from '@mastra/core/agent';
|
|
5
|
+
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
6
7
|
import { saveScorePayloadSchema } from '@mastra/core/scores';
|
|
7
8
|
|
|
8
9
|
// src/storage/index.ts
|
|
@@ -15,154 +16,71 @@ function getTableName({ indexName, schemaName }) {
|
|
|
15
16
|
const quotedSchemaName = schemaName;
|
|
16
17
|
return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (row.test_info) {
|
|
23
|
-
try {
|
|
24
|
-
testInfoValue = typeof row.test_info === "string" ? JSON.parse(row.test_info) : row.test_info;
|
|
25
|
-
} catch {
|
|
26
|
-
}
|
|
19
|
+
function buildDateRangeFilter(dateRange, fieldName) {
|
|
20
|
+
const filters = {};
|
|
21
|
+
if (dateRange?.start) {
|
|
22
|
+
filters[`${fieldName}_gte`] = dateRange.start;
|
|
27
23
|
}
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
resultValue = typeof row.result === "string" ? JSON.parse(row.result) : row.result;
|
|
31
|
-
} catch {
|
|
32
|
-
}
|
|
24
|
+
if (dateRange?.end) {
|
|
25
|
+
filters[`${fieldName}_lte`] = dateRange.end;
|
|
33
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
|
+
});
|
|
34
51
|
return {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
output: row.output,
|
|
38
|
-
result: resultValue,
|
|
39
|
-
metricName: row.metric_name,
|
|
40
|
-
instructions: row.instructions,
|
|
41
|
-
testInfo: testInfoValue,
|
|
42
|
-
globalRunId: row.global_run_id,
|
|
43
|
-
runId: row.run_id,
|
|
44
|
-
createdAt: row.created_at
|
|
52
|
+
sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
|
|
53
|
+
params
|
|
45
54
|
};
|
|
46
55
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
query += " AND test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL";
|
|
61
|
-
} else if (type === "live") {
|
|
62
|
-
query += " AND (test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)";
|
|
63
|
-
}
|
|
64
|
-
query += " ORDER BY created_at DESC";
|
|
65
|
-
const request = this.pool.request();
|
|
66
|
-
request.input("p1", agentName);
|
|
67
|
-
const result = await request.query(query);
|
|
68
|
-
const rows = result.recordset;
|
|
69
|
-
return typeof transformEvalRow === "function" ? rows?.map((row) => transformEvalRow(row)) ?? [] : rows ?? [];
|
|
70
|
-
} catch (error) {
|
|
71
|
-
if (error && error.number === 208 && error.message && error.message.includes("Invalid object name")) {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
console.error("Failed to get evals for the specified agent: " + error?.message);
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
async getEvals(options = {}) {
|
|
79
|
-
const { agentName, type, page = 0, perPage = 100, dateRange } = options;
|
|
80
|
-
const fromDate = dateRange?.start;
|
|
81
|
-
const toDate = dateRange?.end;
|
|
82
|
-
const where = [];
|
|
83
|
-
const params = {};
|
|
84
|
-
if (agentName) {
|
|
85
|
-
where.push("agent_name = @agentName");
|
|
86
|
-
params["agentName"] = agentName;
|
|
87
|
-
}
|
|
88
|
-
if (type === "test") {
|
|
89
|
-
where.push("test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL");
|
|
90
|
-
} else if (type === "live") {
|
|
91
|
-
where.push("(test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)");
|
|
92
|
-
}
|
|
93
|
-
if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
|
|
94
|
-
where.push(`[created_at] >= @fromDate`);
|
|
95
|
-
params[`fromDate`] = fromDate.toISOString();
|
|
96
|
-
}
|
|
97
|
-
if (toDate instanceof Date && !isNaN(toDate.getTime())) {
|
|
98
|
-
where.push(`[created_at] <= @toDate`);
|
|
99
|
-
params[`toDate`] = toDate.toISOString();
|
|
100
|
-
}
|
|
101
|
-
const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
102
|
-
const tableName = getTableName({ indexName: TABLE_EVALS, schemaName: getSchemaName(this.schema) });
|
|
103
|
-
const offset = page * perPage;
|
|
104
|
-
const countQuery = `SELECT COUNT(*) as total FROM ${tableName} ${whereClause}`;
|
|
105
|
-
const dataQuery = `SELECT * FROM ${tableName} ${whereClause} ORDER BY seq_id DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
106
|
-
try {
|
|
107
|
-
const countReq = this.pool.request();
|
|
108
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
109
|
-
if (value instanceof Date) {
|
|
110
|
-
countReq.input(key, sql2.DateTime, value);
|
|
111
|
-
} else {
|
|
112
|
-
countReq.input(key, value);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
const countResult = await countReq.query(countQuery);
|
|
116
|
-
const total = countResult.recordset[0]?.total || 0;
|
|
117
|
-
if (total === 0) {
|
|
118
|
-
return {
|
|
119
|
-
evals: [],
|
|
120
|
-
total: 0,
|
|
121
|
-
page,
|
|
122
|
-
perPage,
|
|
123
|
-
hasMore: false
|
|
124
|
-
};
|
|
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;
|
|
125
69
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
req.input("offset", offset);
|
|
135
|
-
req.input("perPage", perPage);
|
|
136
|
-
const result = await req.query(dataQuery);
|
|
137
|
-
const rows = result.recordset;
|
|
138
|
-
return {
|
|
139
|
-
evals: rows?.map((row) => transformEvalRow(row)) ?? [],
|
|
140
|
-
total,
|
|
141
|
-
page,
|
|
142
|
-
perPage,
|
|
143
|
-
hasMore: offset + (rows?.length ?? 0) < total
|
|
144
|
-
};
|
|
145
|
-
} catch (error) {
|
|
146
|
-
const mastraError = new MastraError(
|
|
147
|
-
{
|
|
148
|
-
id: "MASTRA_STORAGE_MSSQL_STORE_GET_EVALS_FAILED",
|
|
149
|
-
domain: ErrorDomain.STORAGE,
|
|
150
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
151
|
-
details: {
|
|
152
|
-
agentName: agentName || "all",
|
|
153
|
-
type: type || "all",
|
|
154
|
-
page,
|
|
155
|
-
perPage
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
error
|
|
159
|
-
);
|
|
160
|
-
this.logger?.error?.(mastraError.toString());
|
|
161
|
-
this.logger?.trackException(mastraError);
|
|
162
|
-
throw mastraError;
|
|
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;
|
|
163
78
|
}
|
|
164
|
-
}
|
|
165
|
-
|
|
79
|
+
});
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/storage/domains/memory/index.ts
|
|
166
84
|
var MemoryMSSQL = class extends MemoryStorage {
|
|
167
85
|
pool;
|
|
168
86
|
schema;
|
|
@@ -194,7 +112,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
194
112
|
}
|
|
195
113
|
async getThreadById({ threadId }) {
|
|
196
114
|
try {
|
|
197
|
-
const
|
|
115
|
+
const sql5 = `SELECT
|
|
198
116
|
id,
|
|
199
117
|
[resourceId],
|
|
200
118
|
title,
|
|
@@ -205,7 +123,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
205
123
|
WHERE id = @threadId`;
|
|
206
124
|
const request = this.pool.request();
|
|
207
125
|
request.input("threadId", threadId);
|
|
208
|
-
const resultSet = await request.query(
|
|
126
|
+
const resultSet = await request.query(sql5);
|
|
209
127
|
const thread = resultSet.recordset[0] || null;
|
|
210
128
|
if (!thread) {
|
|
211
129
|
return null;
|
|
@@ -251,7 +169,8 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
251
169
|
};
|
|
252
170
|
}
|
|
253
171
|
const orderByField = orderBy === "createdAt" ? "[createdAt]" : "[updatedAt]";
|
|
254
|
-
const
|
|
172
|
+
const dir = (sortDirection || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
|
|
173
|
+
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
255
174
|
const dataRequest = this.pool.request();
|
|
256
175
|
dataRequest.input("resourceId", resourceId);
|
|
257
176
|
dataRequest.input("perPage", perPage);
|
|
@@ -308,7 +227,12 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
308
227
|
req.input("id", thread.id);
|
|
309
228
|
req.input("resourceId", thread.resourceId);
|
|
310
229
|
req.input("title", thread.title);
|
|
311
|
-
|
|
230
|
+
const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
|
|
231
|
+
if (metadata === null) {
|
|
232
|
+
req.input("metadata", sql2.NVarChar, null);
|
|
233
|
+
} else {
|
|
234
|
+
req.input("metadata", metadata);
|
|
235
|
+
}
|
|
312
236
|
req.input("createdAt", sql2.DateTime2, thread.createdAt);
|
|
313
237
|
req.input("updatedAt", sql2.DateTime2, thread.updatedAt);
|
|
314
238
|
await req.query(mergeSql);
|
|
@@ -335,7 +259,8 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
335
259
|
try {
|
|
336
260
|
const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
|
|
337
261
|
const orderByField = orderBy === "createdAt" ? "[createdAt]" : "[updatedAt]";
|
|
338
|
-
const
|
|
262
|
+
const dir = (sortDirection || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
|
|
263
|
+
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir}`;
|
|
339
264
|
const request = this.pool.request();
|
|
340
265
|
request.input("resourceId", resourceId);
|
|
341
266
|
const resultSet = await request.query(dataQuery);
|
|
@@ -378,7 +303,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
378
303
|
};
|
|
379
304
|
try {
|
|
380
305
|
const table = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
|
|
381
|
-
const
|
|
306
|
+
const sql5 = `UPDATE ${table}
|
|
382
307
|
SET title = @title,
|
|
383
308
|
metadata = @metadata,
|
|
384
309
|
[updatedAt] = @updatedAt
|
|
@@ -389,7 +314,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
389
314
|
req.input("title", title);
|
|
390
315
|
req.input("metadata", JSON.stringify(mergedMetadata));
|
|
391
316
|
req.input("updatedAt", /* @__PURE__ */ new Date());
|
|
392
|
-
const result = await req.query(
|
|
317
|
+
const result = await req.query(sql5);
|
|
393
318
|
let thread = result.recordset && result.recordset[0];
|
|
394
319
|
if (thread && "seq_id" in thread) {
|
|
395
320
|
const { seq_id, ...rest } = thread;
|
|
@@ -584,7 +509,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
584
509
|
error
|
|
585
510
|
);
|
|
586
511
|
this.logger?.error?.(mastraError.toString());
|
|
587
|
-
this.logger?.trackException(mastraError);
|
|
512
|
+
this.logger?.trackException?.(mastraError);
|
|
588
513
|
return [];
|
|
589
514
|
}
|
|
590
515
|
}
|
|
@@ -624,10 +549,24 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
624
549
|
error
|
|
625
550
|
);
|
|
626
551
|
this.logger?.error?.(mastraError.toString());
|
|
627
|
-
this.logger?.trackException(mastraError);
|
|
552
|
+
this.logger?.trackException?.(mastraError);
|
|
628
553
|
return [];
|
|
629
554
|
}
|
|
630
555
|
}
|
|
556
|
+
async listMessages(_args) {
|
|
557
|
+
throw new Error(
|
|
558
|
+
`listMessages is not yet implemented by this storage adapter (${this.constructor.name}). This method is currently being rolled out across all storage adapters. Please use getMessages or getMessagesPaginated as an alternative, or wait for the implementation.`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
async listMessagesById({ messageIds }) {
|
|
562
|
+
return this.getMessagesById({ messageIds, format: "v2" });
|
|
563
|
+
}
|
|
564
|
+
async listThreadsByResourceId(args) {
|
|
565
|
+
const { resourceId, limit, offset, orderBy, sortDirection } = args;
|
|
566
|
+
const page = Math.floor(offset / limit);
|
|
567
|
+
const perPage = limit;
|
|
568
|
+
return this.getThreadsByResourceIdPaginated({ resourceId, page, perPage, orderBy, sortDirection });
|
|
569
|
+
}
|
|
631
570
|
async getMessagesPaginated(args) {
|
|
632
571
|
const { threadId, resourceId, format, selectBy } = args;
|
|
633
572
|
const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
|
|
@@ -686,7 +625,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
686
625
|
const parsed = this._parseAndFormatMessages(messages, format);
|
|
687
626
|
return {
|
|
688
627
|
messages: parsed,
|
|
689
|
-
total
|
|
628
|
+
total,
|
|
690
629
|
page,
|
|
691
630
|
perPage,
|
|
692
631
|
hasMore: currentOffset + rows.length < total
|
|
@@ -706,7 +645,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
706
645
|
error
|
|
707
646
|
);
|
|
708
647
|
this.logger?.error?.(mastraError.toString());
|
|
709
|
-
this.logger?.trackException(mastraError);
|
|
648
|
+
this.logger?.trackException?.(mastraError);
|
|
710
649
|
return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
|
|
711
650
|
}
|
|
712
651
|
}
|
|
@@ -973,8 +912,10 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
973
912
|
return null;
|
|
974
913
|
}
|
|
975
914
|
return {
|
|
976
|
-
|
|
977
|
-
|
|
915
|
+
id: result.id,
|
|
916
|
+
createdAt: result.createdAt,
|
|
917
|
+
updatedAt: result.updatedAt,
|
|
918
|
+
workingMemory: result.workingMemory,
|
|
978
919
|
metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
|
|
979
920
|
};
|
|
980
921
|
} catch (error) {
|
|
@@ -988,7 +929,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
988
929
|
error
|
|
989
930
|
);
|
|
990
931
|
this.logger?.error?.(mastraError.toString());
|
|
991
|
-
this.logger?.trackException(mastraError);
|
|
932
|
+
this.logger?.trackException?.(mastraError);
|
|
992
933
|
throw mastraError;
|
|
993
934
|
}
|
|
994
935
|
}
|
|
@@ -997,7 +938,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
997
938
|
tableName: TABLE_RESOURCES,
|
|
998
939
|
record: {
|
|
999
940
|
...resource,
|
|
1000
|
-
metadata:
|
|
941
|
+
metadata: resource.metadata
|
|
1001
942
|
}
|
|
1002
943
|
});
|
|
1003
944
|
return resource;
|
|
@@ -1055,20 +996,337 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
1055
996
|
error
|
|
1056
997
|
);
|
|
1057
998
|
this.logger?.error?.(mastraError.toString());
|
|
1058
|
-
this.logger?.trackException(mastraError);
|
|
999
|
+
this.logger?.trackException?.(mastraError);
|
|
1059
1000
|
throw mastraError;
|
|
1060
1001
|
}
|
|
1061
1002
|
}
|
|
1062
1003
|
};
|
|
1004
|
+
var ObservabilityMSSQL = class extends ObservabilityStorage {
|
|
1005
|
+
pool;
|
|
1006
|
+
operations;
|
|
1007
|
+
schema;
|
|
1008
|
+
constructor({
|
|
1009
|
+
pool,
|
|
1010
|
+
operations,
|
|
1011
|
+
schema
|
|
1012
|
+
}) {
|
|
1013
|
+
super();
|
|
1014
|
+
this.pool = pool;
|
|
1015
|
+
this.operations = operations;
|
|
1016
|
+
this.schema = schema;
|
|
1017
|
+
}
|
|
1018
|
+
get aiTracingStrategy() {
|
|
1019
|
+
return {
|
|
1020
|
+
preferred: "batch-with-updates",
|
|
1021
|
+
supported: ["batch-with-updates", "insert-only"]
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
async createAISpan(span) {
|
|
1025
|
+
try {
|
|
1026
|
+
const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt;
|
|
1027
|
+
const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt;
|
|
1028
|
+
const record = {
|
|
1029
|
+
...span,
|
|
1030
|
+
startedAt,
|
|
1031
|
+
endedAt
|
|
1032
|
+
// Note: createdAt/updatedAt will be set by default values
|
|
1033
|
+
};
|
|
1034
|
+
return this.operations.insert({ tableName: TABLE_AI_SPANS, record });
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
throw new MastraError(
|
|
1037
|
+
{
|
|
1038
|
+
id: "MSSQL_STORE_CREATE_AI_SPAN_FAILED",
|
|
1039
|
+
domain: ErrorDomain.STORAGE,
|
|
1040
|
+
category: ErrorCategory.USER,
|
|
1041
|
+
details: {
|
|
1042
|
+
spanId: span.spanId,
|
|
1043
|
+
traceId: span.traceId,
|
|
1044
|
+
spanType: span.spanType,
|
|
1045
|
+
spanName: span.name
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
error
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
async getAITrace(traceId) {
|
|
1053
|
+
try {
|
|
1054
|
+
const tableName = getTableName({
|
|
1055
|
+
indexName: TABLE_AI_SPANS,
|
|
1056
|
+
schemaName: getSchemaName(this.schema)
|
|
1057
|
+
});
|
|
1058
|
+
const request = this.pool.request();
|
|
1059
|
+
request.input("traceId", traceId);
|
|
1060
|
+
const result = await request.query(
|
|
1061
|
+
`SELECT
|
|
1062
|
+
[traceId], [spanId], [parentSpanId], [name], [scope], [spanType],
|
|
1063
|
+
[attributes], [metadata], [links], [input], [output], [error], [isEvent],
|
|
1064
|
+
[startedAt], [endedAt], [createdAt], [updatedAt]
|
|
1065
|
+
FROM ${tableName}
|
|
1066
|
+
WHERE [traceId] = @traceId
|
|
1067
|
+
ORDER BY [startedAt] DESC`
|
|
1068
|
+
);
|
|
1069
|
+
if (!result.recordset || result.recordset.length === 0) {
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
return {
|
|
1073
|
+
traceId,
|
|
1074
|
+
spans: result.recordset.map(
|
|
1075
|
+
(span) => transformFromSqlRow({
|
|
1076
|
+
tableName: TABLE_AI_SPANS,
|
|
1077
|
+
sqlRow: span
|
|
1078
|
+
})
|
|
1079
|
+
)
|
|
1080
|
+
};
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
throw new MastraError(
|
|
1083
|
+
{
|
|
1084
|
+
id: "MSSQL_STORE_GET_AI_TRACE_FAILED",
|
|
1085
|
+
domain: ErrorDomain.STORAGE,
|
|
1086
|
+
category: ErrorCategory.USER,
|
|
1087
|
+
details: {
|
|
1088
|
+
traceId
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1091
|
+
error
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
async updateAISpan({
|
|
1096
|
+
spanId,
|
|
1097
|
+
traceId,
|
|
1098
|
+
updates
|
|
1099
|
+
}) {
|
|
1100
|
+
try {
|
|
1101
|
+
const data = { ...updates };
|
|
1102
|
+
if (data.endedAt instanceof Date) {
|
|
1103
|
+
data.endedAt = data.endedAt.toISOString();
|
|
1104
|
+
}
|
|
1105
|
+
if (data.startedAt instanceof Date) {
|
|
1106
|
+
data.startedAt = data.startedAt.toISOString();
|
|
1107
|
+
}
|
|
1108
|
+
await this.operations.update({
|
|
1109
|
+
tableName: TABLE_AI_SPANS,
|
|
1110
|
+
keys: { spanId, traceId },
|
|
1111
|
+
data
|
|
1112
|
+
});
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
throw new MastraError(
|
|
1115
|
+
{
|
|
1116
|
+
id: "MSSQL_STORE_UPDATE_AI_SPAN_FAILED",
|
|
1117
|
+
domain: ErrorDomain.STORAGE,
|
|
1118
|
+
category: ErrorCategory.USER,
|
|
1119
|
+
details: {
|
|
1120
|
+
spanId,
|
|
1121
|
+
traceId
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
error
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
async getAITracesPaginated({
|
|
1129
|
+
filters,
|
|
1130
|
+
pagination
|
|
1131
|
+
}) {
|
|
1132
|
+
const page = pagination?.page ?? 0;
|
|
1133
|
+
const perPage = pagination?.perPage ?? 10;
|
|
1134
|
+
const { entityId, entityType, ...actualFilters } = filters || {};
|
|
1135
|
+
const filtersWithDateRange = {
|
|
1136
|
+
...actualFilters,
|
|
1137
|
+
...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
|
|
1138
|
+
parentSpanId: null
|
|
1139
|
+
// Only get root spans for traces
|
|
1140
|
+
};
|
|
1141
|
+
const whereClause = prepareWhereClause(filtersWithDateRange);
|
|
1142
|
+
let actualWhereClause = whereClause.sql;
|
|
1143
|
+
const params = { ...whereClause.params };
|
|
1144
|
+
let currentParamIndex = Object.keys(params).length + 1;
|
|
1145
|
+
if (entityId && entityType) {
|
|
1146
|
+
let name = "";
|
|
1147
|
+
if (entityType === "workflow") {
|
|
1148
|
+
name = `workflow run: '${entityId}'`;
|
|
1149
|
+
} else if (entityType === "agent") {
|
|
1150
|
+
name = `agent run: '${entityId}'`;
|
|
1151
|
+
} else {
|
|
1152
|
+
const error = new MastraError({
|
|
1153
|
+
id: "MSSQL_STORE_GET_AI_TRACES_PAGINATED_FAILED",
|
|
1154
|
+
domain: ErrorDomain.STORAGE,
|
|
1155
|
+
category: ErrorCategory.USER,
|
|
1156
|
+
details: {
|
|
1157
|
+
entityType
|
|
1158
|
+
},
|
|
1159
|
+
text: `Cannot filter by entity type: ${entityType}`
|
|
1160
|
+
});
|
|
1161
|
+
throw error;
|
|
1162
|
+
}
|
|
1163
|
+
const entityParam = `p${currentParamIndex++}`;
|
|
1164
|
+
if (actualWhereClause) {
|
|
1165
|
+
actualWhereClause += ` AND [name] = @${entityParam}`;
|
|
1166
|
+
} else {
|
|
1167
|
+
actualWhereClause = ` WHERE [name] = @${entityParam}`;
|
|
1168
|
+
}
|
|
1169
|
+
params[entityParam] = name;
|
|
1170
|
+
}
|
|
1171
|
+
const tableName = getTableName({
|
|
1172
|
+
indexName: TABLE_AI_SPANS,
|
|
1173
|
+
schemaName: getSchemaName(this.schema)
|
|
1174
|
+
});
|
|
1175
|
+
try {
|
|
1176
|
+
const countRequest = this.pool.request();
|
|
1177
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1178
|
+
countRequest.input(key, value);
|
|
1179
|
+
});
|
|
1180
|
+
const countResult = await countRequest.query(
|
|
1181
|
+
`SELECT COUNT(*) as count FROM ${tableName}${actualWhereClause}`
|
|
1182
|
+
);
|
|
1183
|
+
const total = countResult.recordset[0]?.count ?? 0;
|
|
1184
|
+
if (total === 0) {
|
|
1185
|
+
return {
|
|
1186
|
+
pagination: {
|
|
1187
|
+
total: 0,
|
|
1188
|
+
page,
|
|
1189
|
+
perPage,
|
|
1190
|
+
hasMore: false
|
|
1191
|
+
},
|
|
1192
|
+
spans: []
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
const dataRequest = this.pool.request();
|
|
1196
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1197
|
+
dataRequest.input(key, value);
|
|
1198
|
+
});
|
|
1199
|
+
dataRequest.input("offset", page * perPage);
|
|
1200
|
+
dataRequest.input("limit", perPage);
|
|
1201
|
+
const dataResult = await dataRequest.query(
|
|
1202
|
+
`SELECT * FROM ${tableName}${actualWhereClause} ORDER BY [startedAt] DESC OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
|
|
1203
|
+
);
|
|
1204
|
+
const spans = dataResult.recordset.map(
|
|
1205
|
+
(row) => transformFromSqlRow({
|
|
1206
|
+
tableName: TABLE_AI_SPANS,
|
|
1207
|
+
sqlRow: row
|
|
1208
|
+
})
|
|
1209
|
+
);
|
|
1210
|
+
return {
|
|
1211
|
+
pagination: {
|
|
1212
|
+
total,
|
|
1213
|
+
page,
|
|
1214
|
+
perPage,
|
|
1215
|
+
hasMore: (page + 1) * perPage < total
|
|
1216
|
+
},
|
|
1217
|
+
spans
|
|
1218
|
+
};
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
throw new MastraError(
|
|
1221
|
+
{
|
|
1222
|
+
id: "MSSQL_STORE_GET_AI_TRACES_PAGINATED_FAILED",
|
|
1223
|
+
domain: ErrorDomain.STORAGE,
|
|
1224
|
+
category: ErrorCategory.USER
|
|
1225
|
+
},
|
|
1226
|
+
error
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
async batchCreateAISpans(args) {
|
|
1231
|
+
if (!args.records || args.records.length === 0) {
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
try {
|
|
1235
|
+
await this.operations.batchInsert({
|
|
1236
|
+
tableName: TABLE_AI_SPANS,
|
|
1237
|
+
records: args.records.map((span) => ({
|
|
1238
|
+
...span,
|
|
1239
|
+
startedAt: span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt,
|
|
1240
|
+
endedAt: span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt
|
|
1241
|
+
}))
|
|
1242
|
+
});
|
|
1243
|
+
} catch (error) {
|
|
1244
|
+
throw new MastraError(
|
|
1245
|
+
{
|
|
1246
|
+
id: "MSSQL_STORE_BATCH_CREATE_AI_SPANS_FAILED",
|
|
1247
|
+
domain: ErrorDomain.STORAGE,
|
|
1248
|
+
category: ErrorCategory.USER,
|
|
1249
|
+
details: {
|
|
1250
|
+
count: args.records.length
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1253
|
+
error
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
async batchUpdateAISpans(args) {
|
|
1258
|
+
if (!args.records || args.records.length === 0) {
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
try {
|
|
1262
|
+
const updates = args.records.map(({ traceId, spanId, updates: data }) => {
|
|
1263
|
+
const processedData = { ...data };
|
|
1264
|
+
if (processedData.endedAt instanceof Date) {
|
|
1265
|
+
processedData.endedAt = processedData.endedAt.toISOString();
|
|
1266
|
+
}
|
|
1267
|
+
if (processedData.startedAt instanceof Date) {
|
|
1268
|
+
processedData.startedAt = processedData.startedAt.toISOString();
|
|
1269
|
+
}
|
|
1270
|
+
return {
|
|
1271
|
+
keys: { spanId, traceId },
|
|
1272
|
+
data: processedData
|
|
1273
|
+
};
|
|
1274
|
+
});
|
|
1275
|
+
await this.operations.batchUpdate({
|
|
1276
|
+
tableName: TABLE_AI_SPANS,
|
|
1277
|
+
updates
|
|
1278
|
+
});
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
throw new MastraError(
|
|
1281
|
+
{
|
|
1282
|
+
id: "MSSQL_STORE_BATCH_UPDATE_AI_SPANS_FAILED",
|
|
1283
|
+
domain: ErrorDomain.STORAGE,
|
|
1284
|
+
category: ErrorCategory.USER,
|
|
1285
|
+
details: {
|
|
1286
|
+
count: args.records.length
|
|
1287
|
+
}
|
|
1288
|
+
},
|
|
1289
|
+
error
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
async batchDeleteAITraces(args) {
|
|
1294
|
+
if (!args.traceIds || args.traceIds.length === 0) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
try {
|
|
1298
|
+
const keys = args.traceIds.map((traceId) => ({ traceId }));
|
|
1299
|
+
await this.operations.batchDelete({
|
|
1300
|
+
tableName: TABLE_AI_SPANS,
|
|
1301
|
+
keys
|
|
1302
|
+
});
|
|
1303
|
+
} catch (error) {
|
|
1304
|
+
throw new MastraError(
|
|
1305
|
+
{
|
|
1306
|
+
id: "MSSQL_STORE_BATCH_DELETE_AI_TRACES_FAILED",
|
|
1307
|
+
domain: ErrorDomain.STORAGE,
|
|
1308
|
+
category: ErrorCategory.USER,
|
|
1309
|
+
details: {
|
|
1310
|
+
count: args.traceIds.length
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
error
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1063
1318
|
var StoreOperationsMSSQL = class extends StoreOperations {
|
|
1064
1319
|
pool;
|
|
1065
1320
|
schemaName;
|
|
1066
1321
|
setupSchemaPromise = null;
|
|
1067
1322
|
schemaSetupComplete = void 0;
|
|
1068
|
-
getSqlType(type, isPrimaryKey = false) {
|
|
1323
|
+
getSqlType(type, isPrimaryKey = false, useLargeStorage = false) {
|
|
1069
1324
|
switch (type) {
|
|
1070
1325
|
case "text":
|
|
1071
|
-
|
|
1326
|
+
if (useLargeStorage) {
|
|
1327
|
+
return "NVARCHAR(MAX)";
|
|
1328
|
+
}
|
|
1329
|
+
return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(400)";
|
|
1072
1330
|
case "timestamp":
|
|
1073
1331
|
return "DATETIME2(7)";
|
|
1074
1332
|
case "uuid":
|
|
@@ -1081,6 +1339,8 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1081
1339
|
return "BIGINT";
|
|
1082
1340
|
case "float":
|
|
1083
1341
|
return "FLOAT";
|
|
1342
|
+
case "boolean":
|
|
1343
|
+
return "BIT";
|
|
1084
1344
|
default:
|
|
1085
1345
|
throw new MastraError({
|
|
1086
1346
|
id: "MASTRA_STORAGE_MSSQL_STORE_TYPE_NOT_SUPPORTED",
|
|
@@ -1143,20 +1403,26 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1143
1403
|
}
|
|
1144
1404
|
await this.setupSchemaPromise;
|
|
1145
1405
|
}
|
|
1146
|
-
async insert({
|
|
1406
|
+
async insert({
|
|
1407
|
+
tableName,
|
|
1408
|
+
record,
|
|
1409
|
+
transaction
|
|
1410
|
+
}) {
|
|
1147
1411
|
try {
|
|
1148
|
-
const columns = Object.keys(record)
|
|
1149
|
-
const
|
|
1150
|
-
const paramNames =
|
|
1151
|
-
const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${
|
|
1152
|
-
const request = this.pool.request();
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
request.input(`param${i}`,
|
|
1412
|
+
const columns = Object.keys(record);
|
|
1413
|
+
const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
|
|
1414
|
+
const paramNames = columns.map((_, i) => `@param${i}`);
|
|
1415
|
+
const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${parsedColumns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
|
|
1416
|
+
const request = transaction ? transaction.request() : this.pool.request();
|
|
1417
|
+
columns.forEach((col, i) => {
|
|
1418
|
+
const value = record[col];
|
|
1419
|
+
const preparedValue = this.prepareValue(value, col, tableName);
|
|
1420
|
+
if (preparedValue instanceof Date) {
|
|
1421
|
+
request.input(`param${i}`, sql2.DateTime2, preparedValue);
|
|
1422
|
+
} else if (preparedValue === null || preparedValue === void 0) {
|
|
1423
|
+
request.input(`param${i}`, this.getMssqlType(tableName, col), null);
|
|
1158
1424
|
} else {
|
|
1159
|
-
request.input(`param${i}`,
|
|
1425
|
+
request.input(`param${i}`, preparedValue);
|
|
1160
1426
|
}
|
|
1161
1427
|
});
|
|
1162
1428
|
await request.query(insertSql);
|
|
@@ -1180,7 +1446,7 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1180
1446
|
try {
|
|
1181
1447
|
await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
|
|
1182
1448
|
} catch (truncateError) {
|
|
1183
|
-
if (truncateError
|
|
1449
|
+
if (truncateError?.number === 4712) {
|
|
1184
1450
|
await this.pool.request().query(`DELETE FROM ${fullTableName}`);
|
|
1185
1451
|
} else {
|
|
1186
1452
|
throw truncateError;
|
|
@@ -1203,9 +1469,11 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1203
1469
|
getDefaultValue(type) {
|
|
1204
1470
|
switch (type) {
|
|
1205
1471
|
case "timestamp":
|
|
1206
|
-
return "DEFAULT
|
|
1472
|
+
return "DEFAULT SYSUTCDATETIME()";
|
|
1207
1473
|
case "jsonb":
|
|
1208
1474
|
return "DEFAULT N'{}'";
|
|
1475
|
+
case "boolean":
|
|
1476
|
+
return "DEFAULT 0";
|
|
1209
1477
|
default:
|
|
1210
1478
|
return super.getDefaultValue(type);
|
|
1211
1479
|
}
|
|
@@ -1216,13 +1484,29 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1216
1484
|
}) {
|
|
1217
1485
|
try {
|
|
1218
1486
|
const uniqueConstraintColumns = tableName === TABLE_WORKFLOW_SNAPSHOT ? ["workflow_name", "run_id"] : [];
|
|
1487
|
+
const largeDataColumns = [
|
|
1488
|
+
"workingMemory",
|
|
1489
|
+
"snapshot",
|
|
1490
|
+
"metadata",
|
|
1491
|
+
"content",
|
|
1492
|
+
// messages.content - can be very long conversation content
|
|
1493
|
+
"input",
|
|
1494
|
+
// evals.input - test input data
|
|
1495
|
+
"output",
|
|
1496
|
+
// evals.output - test output data
|
|
1497
|
+
"instructions",
|
|
1498
|
+
// evals.instructions - evaluation instructions
|
|
1499
|
+
"other"
|
|
1500
|
+
// traces.other - additional trace data
|
|
1501
|
+
];
|
|
1219
1502
|
const columns = Object.entries(schema).map(([name, def]) => {
|
|
1220
1503
|
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1221
1504
|
const constraints = [];
|
|
1222
1505
|
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
1223
1506
|
if (!def.nullable) constraints.push("NOT NULL");
|
|
1224
1507
|
const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
|
|
1225
|
-
|
|
1508
|
+
const useLargeStorage = largeDataColumns.includes(name);
|
|
1509
|
+
return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
|
|
1226
1510
|
}).join(",\n");
|
|
1227
1511
|
if (this.schemaName) {
|
|
1228
1512
|
await this.setupSchema();
|
|
@@ -1309,7 +1593,19 @@ ${columns}
|
|
|
1309
1593
|
const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
|
|
1310
1594
|
if (!columnExists) {
|
|
1311
1595
|
const columnDef = schema[columnName];
|
|
1312
|
-
const
|
|
1596
|
+
const largeDataColumns = [
|
|
1597
|
+
"workingMemory",
|
|
1598
|
+
"snapshot",
|
|
1599
|
+
"metadata",
|
|
1600
|
+
"content",
|
|
1601
|
+
"input",
|
|
1602
|
+
"output",
|
|
1603
|
+
"instructions",
|
|
1604
|
+
"other"
|
|
1605
|
+
];
|
|
1606
|
+
const useLargeStorage = largeDataColumns.includes(columnName);
|
|
1607
|
+
const isIndexed = !!columnDef.primaryKey;
|
|
1608
|
+
const sqlType = this.getSqlType(columnDef.type, isIndexed, useLargeStorage);
|
|
1313
1609
|
const nullable = columnDef.nullable === false ? "NOT NULL" : "";
|
|
1314
1610
|
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
|
|
1315
1611
|
const parsedColumnName = parseSqlIdentifier(columnName, "column name");
|
|
@@ -1337,13 +1633,17 @@ ${columns}
|
|
|
1337
1633
|
try {
|
|
1338
1634
|
const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
|
|
1339
1635
|
const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
|
|
1340
|
-
const
|
|
1341
|
-
const sql6 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
|
|
1636
|
+
const sql5 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
|
|
1342
1637
|
const request = this.pool.request();
|
|
1343
|
-
|
|
1344
|
-
|
|
1638
|
+
keyEntries.forEach(([key, value], i) => {
|
|
1639
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1640
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1641
|
+
request.input(`param${i}`, this.getMssqlType(tableName, key), null);
|
|
1642
|
+
} else {
|
|
1643
|
+
request.input(`param${i}`, preparedValue);
|
|
1644
|
+
}
|
|
1345
1645
|
});
|
|
1346
|
-
const resultSet = await request.query(
|
|
1646
|
+
const resultSet = await request.query(sql5);
|
|
1347
1647
|
const result = resultSet.recordset[0] || null;
|
|
1348
1648
|
if (!result) {
|
|
1349
1649
|
return null;
|
|
@@ -1375,7 +1675,7 @@ ${columns}
|
|
|
1375
1675
|
try {
|
|
1376
1676
|
await transaction.begin();
|
|
1377
1677
|
for (const record of records) {
|
|
1378
|
-
await this.insert({ tableName, record });
|
|
1678
|
+
await this.insert({ tableName, record, transaction });
|
|
1379
1679
|
}
|
|
1380
1680
|
await transaction.commit();
|
|
1381
1681
|
} catch (error) {
|
|
@@ -1412,26 +1712,562 @@ ${columns}
|
|
|
1412
1712
|
);
|
|
1413
1713
|
}
|
|
1414
1714
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1715
|
+
/**
|
|
1716
|
+
* Prepares a value for database operations, handling Date objects and JSON serialization
|
|
1717
|
+
*/
|
|
1718
|
+
prepareValue(value, columnName, tableName) {
|
|
1719
|
+
if (value === null || value === void 0) {
|
|
1720
|
+
return value;
|
|
1721
|
+
}
|
|
1722
|
+
if (value instanceof Date) {
|
|
1723
|
+
return value;
|
|
1724
|
+
}
|
|
1725
|
+
const schema = TABLE_SCHEMAS[tableName];
|
|
1726
|
+
const columnSchema = schema?.[columnName];
|
|
1727
|
+
if (columnSchema?.type === "boolean") {
|
|
1728
|
+
return value ? 1 : 0;
|
|
1729
|
+
}
|
|
1730
|
+
if (columnSchema?.type === "jsonb") {
|
|
1731
|
+
return JSON.stringify(value);
|
|
1732
|
+
}
|
|
1733
|
+
if (typeof value === "object") {
|
|
1734
|
+
return JSON.stringify(value);
|
|
1735
|
+
}
|
|
1736
|
+
return value;
|
|
1421
1737
|
}
|
|
1422
|
-
|
|
1738
|
+
/**
|
|
1739
|
+
* Maps TABLE_SCHEMAS types to mssql param types (used when value is null)
|
|
1740
|
+
*/
|
|
1741
|
+
getMssqlType(tableName, columnName) {
|
|
1742
|
+
const col = TABLE_SCHEMAS[tableName]?.[columnName];
|
|
1743
|
+
switch (col?.type) {
|
|
1744
|
+
case "text":
|
|
1745
|
+
return sql2.NVarChar;
|
|
1746
|
+
case "timestamp":
|
|
1747
|
+
return sql2.DateTime2;
|
|
1748
|
+
case "uuid":
|
|
1749
|
+
return sql2.UniqueIdentifier;
|
|
1750
|
+
case "jsonb":
|
|
1751
|
+
return sql2.NVarChar;
|
|
1752
|
+
case "integer":
|
|
1753
|
+
return sql2.Int;
|
|
1754
|
+
case "bigint":
|
|
1755
|
+
return sql2.BigInt;
|
|
1756
|
+
case "float":
|
|
1757
|
+
return sql2.Float;
|
|
1758
|
+
case "boolean":
|
|
1759
|
+
return sql2.Bit;
|
|
1760
|
+
default:
|
|
1761
|
+
return sql2.NVarChar;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Update a single record in the database
|
|
1766
|
+
*/
|
|
1767
|
+
async update({
|
|
1768
|
+
tableName,
|
|
1769
|
+
keys,
|
|
1770
|
+
data,
|
|
1771
|
+
transaction
|
|
1772
|
+
}) {
|
|
1773
|
+
try {
|
|
1774
|
+
if (!data || Object.keys(data).length === 0) {
|
|
1775
|
+
throw new MastraError({
|
|
1776
|
+
id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_DATA",
|
|
1777
|
+
domain: ErrorDomain.STORAGE,
|
|
1778
|
+
category: ErrorCategory.USER,
|
|
1779
|
+
text: "Cannot update with empty data payload"
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
if (!keys || Object.keys(keys).length === 0) {
|
|
1783
|
+
throw new MastraError({
|
|
1784
|
+
id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_KEYS",
|
|
1785
|
+
domain: ErrorDomain.STORAGE,
|
|
1786
|
+
category: ErrorCategory.USER,
|
|
1787
|
+
text: "Cannot update without keys to identify records"
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
const setClauses = [];
|
|
1791
|
+
const request = transaction ? transaction.request() : this.pool.request();
|
|
1792
|
+
let paramIndex = 0;
|
|
1793
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
1794
|
+
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
1795
|
+
const paramName = `set${paramIndex++}`;
|
|
1796
|
+
setClauses.push(`[${parsedKey}] = @${paramName}`);
|
|
1797
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1798
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1799
|
+
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1800
|
+
} else {
|
|
1801
|
+
request.input(paramName, preparedValue);
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
const whereConditions = [];
|
|
1805
|
+
Object.entries(keys).forEach(([key, value]) => {
|
|
1806
|
+
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
1807
|
+
const paramName = `where${paramIndex++}`;
|
|
1808
|
+
whereConditions.push(`[${parsedKey}] = @${paramName}`);
|
|
1809
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1810
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1811
|
+
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1812
|
+
} else {
|
|
1813
|
+
request.input(paramName, preparedValue);
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
const tableName_ = getTableName({
|
|
1817
|
+
indexName: tableName,
|
|
1818
|
+
schemaName: getSchemaName(this.schemaName)
|
|
1819
|
+
});
|
|
1820
|
+
const updateSql = `UPDATE ${tableName_} SET ${setClauses.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
|
|
1821
|
+
await request.query(updateSql);
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
throw new MastraError(
|
|
1824
|
+
{
|
|
1825
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_FAILED",
|
|
1826
|
+
domain: ErrorDomain.STORAGE,
|
|
1827
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1828
|
+
details: {
|
|
1829
|
+
tableName
|
|
1830
|
+
}
|
|
1831
|
+
},
|
|
1832
|
+
error
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Update multiple records in a single batch transaction
|
|
1838
|
+
*/
|
|
1839
|
+
async batchUpdate({
|
|
1840
|
+
tableName,
|
|
1841
|
+
updates
|
|
1842
|
+
}) {
|
|
1843
|
+
const transaction = this.pool.transaction();
|
|
1844
|
+
try {
|
|
1845
|
+
await transaction.begin();
|
|
1846
|
+
for (const { keys, data } of updates) {
|
|
1847
|
+
await this.update({ tableName, keys, data, transaction });
|
|
1848
|
+
}
|
|
1849
|
+
await transaction.commit();
|
|
1850
|
+
} catch (error) {
|
|
1851
|
+
await transaction.rollback();
|
|
1852
|
+
throw new MastraError(
|
|
1853
|
+
{
|
|
1854
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_UPDATE_FAILED",
|
|
1855
|
+
domain: ErrorDomain.STORAGE,
|
|
1856
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1857
|
+
details: {
|
|
1858
|
+
tableName,
|
|
1859
|
+
numberOfRecords: updates.length
|
|
1860
|
+
}
|
|
1861
|
+
},
|
|
1862
|
+
error
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Delete multiple records by keys
|
|
1868
|
+
*/
|
|
1869
|
+
async batchDelete({ tableName, keys }) {
|
|
1870
|
+
if (keys.length === 0) {
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
const tableName_ = getTableName({
|
|
1874
|
+
indexName: tableName,
|
|
1875
|
+
schemaName: getSchemaName(this.schemaName)
|
|
1876
|
+
});
|
|
1877
|
+
const transaction = this.pool.transaction();
|
|
1878
|
+
try {
|
|
1879
|
+
await transaction.begin();
|
|
1880
|
+
for (const keySet of keys) {
|
|
1881
|
+
const conditions = [];
|
|
1882
|
+
const request = transaction.request();
|
|
1883
|
+
let paramIndex = 0;
|
|
1884
|
+
Object.entries(keySet).forEach(([key, value]) => {
|
|
1885
|
+
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
1886
|
+
const paramName = `p${paramIndex++}`;
|
|
1887
|
+
conditions.push(`[${parsedKey}] = @${paramName}`);
|
|
1888
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1889
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1890
|
+
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1891
|
+
} else {
|
|
1892
|
+
request.input(paramName, preparedValue);
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
const deleteSql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
|
|
1896
|
+
await request.query(deleteSql);
|
|
1897
|
+
}
|
|
1898
|
+
await transaction.commit();
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
await transaction.rollback();
|
|
1901
|
+
throw new MastraError(
|
|
1902
|
+
{
|
|
1903
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_DELETE_FAILED",
|
|
1904
|
+
domain: ErrorDomain.STORAGE,
|
|
1905
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1906
|
+
details: {
|
|
1907
|
+
tableName,
|
|
1908
|
+
numberOfRecords: keys.length
|
|
1909
|
+
}
|
|
1910
|
+
},
|
|
1911
|
+
error
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Create a new index on a table
|
|
1917
|
+
*/
|
|
1918
|
+
async createIndex(options) {
|
|
1919
|
+
try {
|
|
1920
|
+
const { name, table, columns, unique = false, where } = options;
|
|
1921
|
+
const schemaName = this.schemaName || "dbo";
|
|
1922
|
+
const fullTableName = getTableName({
|
|
1923
|
+
indexName: table,
|
|
1924
|
+
schemaName: getSchemaName(this.schemaName)
|
|
1925
|
+
});
|
|
1926
|
+
const indexNameSafe = parseSqlIdentifier(name, "index name");
|
|
1927
|
+
const checkRequest = this.pool.request();
|
|
1928
|
+
checkRequest.input("indexName", indexNameSafe);
|
|
1929
|
+
checkRequest.input("schemaName", schemaName);
|
|
1930
|
+
checkRequest.input("tableName", table);
|
|
1931
|
+
const indexExists = await checkRequest.query(`
|
|
1932
|
+
SELECT 1 as found
|
|
1933
|
+
FROM sys.indexes i
|
|
1934
|
+
INNER JOIN sys.tables t ON i.object_id = t.object_id
|
|
1935
|
+
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
1936
|
+
WHERE i.name = @indexName
|
|
1937
|
+
AND s.name = @schemaName
|
|
1938
|
+
AND t.name = @tableName
|
|
1939
|
+
`);
|
|
1940
|
+
if (indexExists.recordset && indexExists.recordset.length > 0) {
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
const uniqueStr = unique ? "UNIQUE " : "";
|
|
1944
|
+
const columnsStr = columns.map((col) => {
|
|
1945
|
+
if (col.includes(" DESC") || col.includes(" ASC")) {
|
|
1946
|
+
const [colName, ...modifiers] = col.split(" ");
|
|
1947
|
+
if (!colName) {
|
|
1948
|
+
throw new Error(`Invalid column specification: ${col}`);
|
|
1949
|
+
}
|
|
1950
|
+
return `[${parseSqlIdentifier(colName, "column name")}] ${modifiers.join(" ")}`;
|
|
1951
|
+
}
|
|
1952
|
+
return `[${parseSqlIdentifier(col, "column name")}]`;
|
|
1953
|
+
}).join(", ");
|
|
1954
|
+
const whereStr = where ? ` WHERE ${where}` : "";
|
|
1955
|
+
const createIndexSql = `CREATE ${uniqueStr}INDEX [${indexNameSafe}] ON ${fullTableName} (${columnsStr})${whereStr}`;
|
|
1956
|
+
await this.pool.request().query(createIndexSql);
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
throw new MastraError(
|
|
1959
|
+
{
|
|
1960
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_CREATE_FAILED",
|
|
1961
|
+
domain: ErrorDomain.STORAGE,
|
|
1962
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1963
|
+
details: {
|
|
1964
|
+
indexName: options.name,
|
|
1965
|
+
tableName: options.table
|
|
1966
|
+
}
|
|
1967
|
+
},
|
|
1968
|
+
error
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Drop an existing index
|
|
1974
|
+
*/
|
|
1975
|
+
async dropIndex(indexName) {
|
|
1976
|
+
try {
|
|
1977
|
+
const schemaName = this.schemaName || "dbo";
|
|
1978
|
+
const indexNameSafe = parseSqlIdentifier(indexName, "index name");
|
|
1979
|
+
const checkRequest = this.pool.request();
|
|
1980
|
+
checkRequest.input("indexName", indexNameSafe);
|
|
1981
|
+
checkRequest.input("schemaName", schemaName);
|
|
1982
|
+
const result = await checkRequest.query(`
|
|
1983
|
+
SELECT t.name as table_name
|
|
1984
|
+
FROM sys.indexes i
|
|
1985
|
+
INNER JOIN sys.tables t ON i.object_id = t.object_id
|
|
1986
|
+
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
1987
|
+
WHERE i.name = @indexName
|
|
1988
|
+
AND s.name = @schemaName
|
|
1989
|
+
`);
|
|
1990
|
+
if (!result.recordset || result.recordset.length === 0) {
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
if (result.recordset.length > 1) {
|
|
1994
|
+
const tables = result.recordset.map((r) => r.table_name).join(", ");
|
|
1995
|
+
throw new MastraError({
|
|
1996
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_AMBIGUOUS",
|
|
1997
|
+
domain: ErrorDomain.STORAGE,
|
|
1998
|
+
category: ErrorCategory.USER,
|
|
1999
|
+
text: `Index "${indexNameSafe}" exists on multiple tables (${tables}) in schema "${schemaName}". Please drop indexes manually or ensure unique index names.`
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
const tableName = result.recordset[0].table_name;
|
|
2003
|
+
const fullTableName = getTableName({
|
|
2004
|
+
indexName: tableName,
|
|
2005
|
+
schemaName: getSchemaName(this.schemaName)
|
|
2006
|
+
});
|
|
2007
|
+
const dropSql = `DROP INDEX [${indexNameSafe}] ON ${fullTableName}`;
|
|
2008
|
+
await this.pool.request().query(dropSql);
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
throw new MastraError(
|
|
2011
|
+
{
|
|
2012
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_DROP_FAILED",
|
|
2013
|
+
domain: ErrorDomain.STORAGE,
|
|
2014
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2015
|
+
details: {
|
|
2016
|
+
indexName
|
|
2017
|
+
}
|
|
2018
|
+
},
|
|
2019
|
+
error
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
/**
|
|
2024
|
+
* List indexes for a specific table or all tables
|
|
2025
|
+
*/
|
|
2026
|
+
async listIndexes(tableName) {
|
|
2027
|
+
try {
|
|
2028
|
+
const schemaName = this.schemaName || "dbo";
|
|
2029
|
+
let query;
|
|
2030
|
+
const request = this.pool.request();
|
|
2031
|
+
request.input("schemaName", schemaName);
|
|
2032
|
+
if (tableName) {
|
|
2033
|
+
query = `
|
|
2034
|
+
SELECT
|
|
2035
|
+
i.name as name,
|
|
2036
|
+
o.name as [table],
|
|
2037
|
+
i.is_unique as is_unique,
|
|
2038
|
+
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
|
|
2039
|
+
FROM sys.indexes i
|
|
2040
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2041
|
+
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2042
|
+
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2043
|
+
WHERE sch.name = @schemaName
|
|
2044
|
+
AND o.name = @tableName
|
|
2045
|
+
AND i.name IS NOT NULL
|
|
2046
|
+
GROUP BY i.name, o.name, i.is_unique
|
|
2047
|
+
`;
|
|
2048
|
+
request.input("tableName", tableName);
|
|
2049
|
+
} else {
|
|
2050
|
+
query = `
|
|
2051
|
+
SELECT
|
|
2052
|
+
i.name as name,
|
|
2053
|
+
o.name as [table],
|
|
2054
|
+
i.is_unique as is_unique,
|
|
2055
|
+
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
|
|
2056
|
+
FROM sys.indexes i
|
|
2057
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2058
|
+
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2059
|
+
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2060
|
+
WHERE sch.name = @schemaName
|
|
2061
|
+
AND i.name IS NOT NULL
|
|
2062
|
+
GROUP BY i.name, o.name, i.is_unique
|
|
2063
|
+
`;
|
|
2064
|
+
}
|
|
2065
|
+
const result = await request.query(query);
|
|
2066
|
+
const indexes = [];
|
|
2067
|
+
for (const row of result.recordset) {
|
|
2068
|
+
const colRequest = this.pool.request();
|
|
2069
|
+
colRequest.input("indexName", row.name);
|
|
2070
|
+
colRequest.input("schemaName", schemaName);
|
|
2071
|
+
const colResult = await colRequest.query(`
|
|
2072
|
+
SELECT c.name as column_name
|
|
2073
|
+
FROM sys.indexes i
|
|
2074
|
+
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
2075
|
+
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
2076
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2077
|
+
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
|
2078
|
+
WHERE i.name = @indexName
|
|
2079
|
+
AND s.name = @schemaName
|
|
2080
|
+
ORDER BY ic.key_ordinal
|
|
2081
|
+
`);
|
|
2082
|
+
indexes.push({
|
|
2083
|
+
name: row.name,
|
|
2084
|
+
table: row.table,
|
|
2085
|
+
columns: colResult.recordset.map((c) => c.column_name),
|
|
2086
|
+
unique: row.is_unique || false,
|
|
2087
|
+
size: row.size || "0 MB",
|
|
2088
|
+
definition: ""
|
|
2089
|
+
// MSSQL doesn't store definition like PG
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
return indexes;
|
|
2093
|
+
} catch (error) {
|
|
2094
|
+
throw new MastraError(
|
|
2095
|
+
{
|
|
2096
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_LIST_FAILED",
|
|
2097
|
+
domain: ErrorDomain.STORAGE,
|
|
2098
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2099
|
+
details: tableName ? {
|
|
2100
|
+
tableName
|
|
2101
|
+
} : {}
|
|
2102
|
+
},
|
|
2103
|
+
error
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Get detailed statistics for a specific index
|
|
2109
|
+
*/
|
|
2110
|
+
async describeIndex(indexName) {
|
|
2111
|
+
try {
|
|
2112
|
+
const schemaName = this.schemaName || "dbo";
|
|
2113
|
+
const request = this.pool.request();
|
|
2114
|
+
request.input("indexName", indexName);
|
|
2115
|
+
request.input("schemaName", schemaName);
|
|
2116
|
+
const query = `
|
|
2117
|
+
SELECT
|
|
2118
|
+
i.name as name,
|
|
2119
|
+
o.name as [table],
|
|
2120
|
+
i.is_unique as is_unique,
|
|
2121
|
+
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size,
|
|
2122
|
+
i.type_desc as method,
|
|
2123
|
+
ISNULL(us.user_scans, 0) as scans,
|
|
2124
|
+
ISNULL(us.user_seeks + us.user_scans, 0) as tuples_read,
|
|
2125
|
+
ISNULL(us.user_lookups, 0) as tuples_fetched
|
|
2126
|
+
FROM sys.indexes i
|
|
2127
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2128
|
+
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2129
|
+
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2130
|
+
LEFT JOIN sys.dm_db_index_usage_stats us ON i.object_id = us.object_id AND i.index_id = us.index_id
|
|
2131
|
+
WHERE i.name = @indexName
|
|
2132
|
+
AND sch.name = @schemaName
|
|
2133
|
+
GROUP BY i.name, o.name, i.is_unique, i.type_desc, us.user_seeks, us.user_scans, us.user_lookups
|
|
2134
|
+
`;
|
|
2135
|
+
const result = await request.query(query);
|
|
2136
|
+
if (!result.recordset || result.recordset.length === 0) {
|
|
2137
|
+
throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
|
|
2138
|
+
}
|
|
2139
|
+
const row = result.recordset[0];
|
|
2140
|
+
const colRequest = this.pool.request();
|
|
2141
|
+
colRequest.input("indexName", indexName);
|
|
2142
|
+
colRequest.input("schemaName", schemaName);
|
|
2143
|
+
const colResult = await colRequest.query(`
|
|
2144
|
+
SELECT c.name as column_name
|
|
2145
|
+
FROM sys.indexes i
|
|
2146
|
+
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
2147
|
+
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
2148
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2149
|
+
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
|
2150
|
+
WHERE i.name = @indexName
|
|
2151
|
+
AND s.name = @schemaName
|
|
2152
|
+
ORDER BY ic.key_ordinal
|
|
2153
|
+
`);
|
|
2154
|
+
return {
|
|
2155
|
+
name: row.name,
|
|
2156
|
+
table: row.table,
|
|
2157
|
+
columns: colResult.recordset.map((c) => c.column_name),
|
|
2158
|
+
unique: row.is_unique || false,
|
|
2159
|
+
size: row.size || "0 MB",
|
|
2160
|
+
definition: "",
|
|
2161
|
+
method: row.method?.toLowerCase() || "nonclustered",
|
|
2162
|
+
scans: Number(row.scans) || 0,
|
|
2163
|
+
tuples_read: Number(row.tuples_read) || 0,
|
|
2164
|
+
tuples_fetched: Number(row.tuples_fetched) || 0
|
|
2165
|
+
};
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
throw new MastraError(
|
|
2168
|
+
{
|
|
2169
|
+
id: "MASTRA_STORAGE_MSSQL_INDEX_DESCRIBE_FAILED",
|
|
2170
|
+
domain: ErrorDomain.STORAGE,
|
|
2171
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2172
|
+
details: {
|
|
2173
|
+
indexName
|
|
2174
|
+
}
|
|
2175
|
+
},
|
|
2176
|
+
error
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
/**
|
|
2181
|
+
* Returns definitions for automatic performance indexes
|
|
2182
|
+
* IMPORTANT: Uses seq_id DESC instead of createdAt DESC for MSSQL due to millisecond accuracy limitations
|
|
2183
|
+
* NOTE: Using NVARCHAR(400) for text columns (800 bytes) leaves room for composite indexes
|
|
2184
|
+
*/
|
|
2185
|
+
getAutomaticIndexDefinitions() {
|
|
2186
|
+
const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
|
|
2187
|
+
return [
|
|
2188
|
+
// Composite indexes for optimal filtering + sorting performance
|
|
2189
|
+
// NVARCHAR(400) = 800 bytes, plus BIGINT (8 bytes) = 808 bytes total (under 900-byte limit)
|
|
2190
|
+
{
|
|
2191
|
+
name: `${schemaPrefix}mastra_threads_resourceid_seqid_idx`,
|
|
2192
|
+
table: TABLE_THREADS,
|
|
2193
|
+
columns: ["resourceId", "seq_id DESC"]
|
|
2194
|
+
},
|
|
2195
|
+
{
|
|
2196
|
+
name: `${schemaPrefix}mastra_messages_thread_id_seqid_idx`,
|
|
2197
|
+
table: TABLE_MESSAGES,
|
|
2198
|
+
columns: ["thread_id", "seq_id DESC"]
|
|
2199
|
+
},
|
|
2200
|
+
{
|
|
2201
|
+
name: `${schemaPrefix}mastra_traces_name_seqid_idx`,
|
|
2202
|
+
table: TABLE_TRACES,
|
|
2203
|
+
columns: ["name", "seq_id DESC"]
|
|
2204
|
+
},
|
|
2205
|
+
{
|
|
2206
|
+
name: `${schemaPrefix}mastra_scores_trace_id_span_id_seqid_idx`,
|
|
2207
|
+
table: TABLE_SCORERS,
|
|
2208
|
+
columns: ["traceId", "spanId", "seq_id DESC"]
|
|
2209
|
+
},
|
|
2210
|
+
// AI Spans indexes for optimal trace querying
|
|
2211
|
+
{
|
|
2212
|
+
name: `${schemaPrefix}mastra_ai_spans_traceid_startedat_idx`,
|
|
2213
|
+
table: TABLE_AI_SPANS,
|
|
2214
|
+
columns: ["traceId", "startedAt DESC"]
|
|
2215
|
+
},
|
|
2216
|
+
{
|
|
2217
|
+
name: `${schemaPrefix}mastra_ai_spans_parentspanid_startedat_idx`,
|
|
2218
|
+
table: TABLE_AI_SPANS,
|
|
2219
|
+
columns: ["parentSpanId", "startedAt DESC"]
|
|
2220
|
+
},
|
|
2221
|
+
{
|
|
2222
|
+
name: `${schemaPrefix}mastra_ai_spans_name_idx`,
|
|
2223
|
+
table: TABLE_AI_SPANS,
|
|
2224
|
+
columns: ["name"]
|
|
2225
|
+
},
|
|
2226
|
+
{
|
|
2227
|
+
name: `${schemaPrefix}mastra_ai_spans_spantype_startedat_idx`,
|
|
2228
|
+
table: TABLE_AI_SPANS,
|
|
2229
|
+
columns: ["spanType", "startedAt DESC"]
|
|
2230
|
+
}
|
|
2231
|
+
];
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Creates automatic indexes for optimal query performance
|
|
2235
|
+
* Uses getAutomaticIndexDefinitions() to determine which indexes to create
|
|
2236
|
+
*/
|
|
2237
|
+
async createAutomaticIndexes() {
|
|
2238
|
+
try {
|
|
2239
|
+
const indexes = this.getAutomaticIndexDefinitions();
|
|
2240
|
+
for (const indexOptions of indexes) {
|
|
2241
|
+
try {
|
|
2242
|
+
await this.createIndex(indexOptions);
|
|
2243
|
+
} catch (error) {
|
|
2244
|
+
this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
} catch (error) {
|
|
2248
|
+
throw new MastraError(
|
|
2249
|
+
{
|
|
2250
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
|
|
2251
|
+
domain: ErrorDomain.STORAGE,
|
|
2252
|
+
category: ErrorCategory.THIRD_PARTY
|
|
2253
|
+
},
|
|
2254
|
+
error
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
1423
2259
|
function transformScoreRow(row) {
|
|
1424
2260
|
return {
|
|
1425
2261
|
...row,
|
|
1426
|
-
input:
|
|
1427
|
-
scorer:
|
|
1428
|
-
preprocessStepResult:
|
|
1429
|
-
analyzeStepResult:
|
|
1430
|
-
metadata:
|
|
1431
|
-
output:
|
|
1432
|
-
additionalContext:
|
|
1433
|
-
|
|
1434
|
-
entity:
|
|
2262
|
+
input: safelyParseJSON(row.input),
|
|
2263
|
+
scorer: safelyParseJSON(row.scorer),
|
|
2264
|
+
preprocessStepResult: safelyParseJSON(row.preprocessStepResult),
|
|
2265
|
+
analyzeStepResult: safelyParseJSON(row.analyzeStepResult),
|
|
2266
|
+
metadata: safelyParseJSON(row.metadata),
|
|
2267
|
+
output: safelyParseJSON(row.output),
|
|
2268
|
+
additionalContext: safelyParseJSON(row.additionalContext),
|
|
2269
|
+
requestContext: safelyParseJSON(row.requestContext),
|
|
2270
|
+
entity: safelyParseJSON(row.entity),
|
|
1435
2271
|
createdAt: row.createdAt,
|
|
1436
2272
|
updatedAt: row.updatedAt
|
|
1437
2273
|
};
|
|
@@ -1488,7 +2324,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1488
2324
|
);
|
|
1489
2325
|
}
|
|
1490
2326
|
try {
|
|
1491
|
-
const scoreId =
|
|
2327
|
+
const scoreId = randomUUID();
|
|
1492
2328
|
const {
|
|
1493
2329
|
scorer,
|
|
1494
2330
|
preprocessStepResult,
|
|
@@ -1497,7 +2333,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1497
2333
|
input,
|
|
1498
2334
|
output,
|
|
1499
2335
|
additionalContext,
|
|
1500
|
-
|
|
2336
|
+
requestContext,
|
|
1501
2337
|
entity,
|
|
1502
2338
|
...rest
|
|
1503
2339
|
} = validatedScore;
|
|
@@ -1506,15 +2342,15 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1506
2342
|
record: {
|
|
1507
2343
|
id: scoreId,
|
|
1508
2344
|
...rest,
|
|
1509
|
-
input:
|
|
1510
|
-
output:
|
|
1511
|
-
preprocessStepResult: preprocessStepResult
|
|
1512
|
-
analyzeStepResult: analyzeStepResult
|
|
1513
|
-
metadata: metadata
|
|
1514
|
-
additionalContext: additionalContext
|
|
1515
|
-
|
|
1516
|
-
entity: entity
|
|
1517
|
-
scorer: scorer
|
|
2345
|
+
input: input || "",
|
|
2346
|
+
output: output || "",
|
|
2347
|
+
preprocessStepResult: preprocessStepResult || null,
|
|
2348
|
+
analyzeStepResult: analyzeStepResult || null,
|
|
2349
|
+
metadata: metadata || null,
|
|
2350
|
+
additionalContext: additionalContext || null,
|
|
2351
|
+
requestContext: requestContext || null,
|
|
2352
|
+
entity: entity || null,
|
|
2353
|
+
scorer: scorer || null,
|
|
1518
2354
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1519
2355
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1520
2356
|
}
|
|
@@ -1534,14 +2370,37 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1534
2370
|
}
|
|
1535
2371
|
async getScoresByScorerId({
|
|
1536
2372
|
scorerId,
|
|
1537
|
-
pagination
|
|
2373
|
+
pagination,
|
|
2374
|
+
entityId,
|
|
2375
|
+
entityType,
|
|
2376
|
+
source
|
|
1538
2377
|
}) {
|
|
1539
2378
|
try {
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2379
|
+
const conditions = ["[scorerId] = @p1"];
|
|
2380
|
+
const params = { p1: scorerId };
|
|
2381
|
+
let paramIndex = 2;
|
|
2382
|
+
if (entityId) {
|
|
2383
|
+
conditions.push(`[entityId] = @p${paramIndex}`);
|
|
2384
|
+
params[`p${paramIndex}`] = entityId;
|
|
2385
|
+
paramIndex++;
|
|
2386
|
+
}
|
|
2387
|
+
if (entityType) {
|
|
2388
|
+
conditions.push(`[entityType] = @p${paramIndex}`);
|
|
2389
|
+
params[`p${paramIndex}`] = entityType;
|
|
2390
|
+
paramIndex++;
|
|
2391
|
+
}
|
|
2392
|
+
if (source) {
|
|
2393
|
+
conditions.push(`[source] = @p${paramIndex}`);
|
|
2394
|
+
params[`p${paramIndex}`] = source;
|
|
2395
|
+
paramIndex++;
|
|
2396
|
+
}
|
|
2397
|
+
const whereClause = conditions.join(" AND ");
|
|
2398
|
+
const tableName = getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) });
|
|
2399
|
+
const countRequest = this.pool.request();
|
|
2400
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
2401
|
+
countRequest.input(key, value);
|
|
2402
|
+
});
|
|
2403
|
+
const totalResult = await countRequest.query(`SELECT COUNT(*) as count FROM ${tableName} WHERE ${whereClause}`);
|
|
1545
2404
|
const total = totalResult.recordset[0]?.count || 0;
|
|
1546
2405
|
if (total === 0) {
|
|
1547
2406
|
return {
|
|
@@ -1555,12 +2414,13 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1555
2414
|
};
|
|
1556
2415
|
}
|
|
1557
2416
|
const dataRequest = this.pool.request();
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
2417
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
2418
|
+
dataRequest.input(key, value);
|
|
2419
|
+
});
|
|
2420
|
+
dataRequest.input("perPage", pagination.perPage);
|
|
2421
|
+
dataRequest.input("offset", pagination.page * pagination.perPage);
|
|
2422
|
+
const dataQuery = `SELECT * FROM ${tableName} WHERE ${whereClause} ORDER BY [createdAt] DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
2423
|
+
const result = await dataRequest.query(dataQuery);
|
|
1564
2424
|
return {
|
|
1565
2425
|
pagination: {
|
|
1566
2426
|
total: Number(total),
|
|
@@ -1740,24 +2600,6 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1740
2600
|
}
|
|
1741
2601
|
}
|
|
1742
2602
|
};
|
|
1743
|
-
function parseWorkflowRun(row) {
|
|
1744
|
-
let parsedSnapshot = row.snapshot;
|
|
1745
|
-
if (typeof parsedSnapshot === "string") {
|
|
1746
|
-
try {
|
|
1747
|
-
parsedSnapshot = JSON.parse(row.snapshot);
|
|
1748
|
-
} catch (e) {
|
|
1749
|
-
console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
return {
|
|
1753
|
-
workflowName: row.workflow_name,
|
|
1754
|
-
runId: row.run_id,
|
|
1755
|
-
snapshot: parsedSnapshot,
|
|
1756
|
-
createdAt: row.createdAt,
|
|
1757
|
-
updatedAt: row.updatedAt,
|
|
1758
|
-
resourceId: row.resourceId
|
|
1759
|
-
};
|
|
1760
|
-
}
|
|
1761
2603
|
var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
1762
2604
|
pool;
|
|
1763
2605
|
operations;
|
|
@@ -1772,21 +2614,163 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1772
2614
|
this.operations = operations;
|
|
1773
2615
|
this.schema = schema;
|
|
1774
2616
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
2617
|
+
parseWorkflowRun(row) {
|
|
2618
|
+
let parsedSnapshot = row.snapshot;
|
|
2619
|
+
if (typeof parsedSnapshot === "string") {
|
|
2620
|
+
try {
|
|
2621
|
+
parsedSnapshot = JSON.parse(row.snapshot);
|
|
2622
|
+
} catch (e) {
|
|
2623
|
+
this.logger?.warn?.(`Failed to parse snapshot for workflow ${row.workflow_name}:`, e);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
return {
|
|
2627
|
+
workflowName: row.workflow_name,
|
|
2628
|
+
runId: row.run_id,
|
|
2629
|
+
snapshot: parsedSnapshot,
|
|
2630
|
+
createdAt: row.createdAt,
|
|
2631
|
+
updatedAt: row.updatedAt,
|
|
2632
|
+
resourceId: row.resourceId
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
async updateWorkflowResults({
|
|
2636
|
+
workflowName,
|
|
2637
|
+
runId,
|
|
2638
|
+
stepId,
|
|
2639
|
+
result,
|
|
2640
|
+
requestContext
|
|
1781
2641
|
}) {
|
|
1782
|
-
|
|
2642
|
+
const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
2643
|
+
const transaction = this.pool.transaction();
|
|
2644
|
+
try {
|
|
2645
|
+
await transaction.begin();
|
|
2646
|
+
const selectRequest = new sql2.Request(transaction);
|
|
2647
|
+
selectRequest.input("workflow_name", workflowName);
|
|
2648
|
+
selectRequest.input("run_id", runId);
|
|
2649
|
+
const existingSnapshotResult = await selectRequest.query(
|
|
2650
|
+
`SELECT snapshot FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
2651
|
+
);
|
|
2652
|
+
let snapshot;
|
|
2653
|
+
if (!existingSnapshotResult.recordset || existingSnapshotResult.recordset.length === 0) {
|
|
2654
|
+
snapshot = {
|
|
2655
|
+
context: {},
|
|
2656
|
+
activePaths: [],
|
|
2657
|
+
timestamp: Date.now(),
|
|
2658
|
+
suspendedPaths: {},
|
|
2659
|
+
resumeLabels: {},
|
|
2660
|
+
serializedStepGraph: [],
|
|
2661
|
+
value: {},
|
|
2662
|
+
waitingPaths: {},
|
|
2663
|
+
status: "pending",
|
|
2664
|
+
runId,
|
|
2665
|
+
requestContext: {}
|
|
2666
|
+
};
|
|
2667
|
+
} else {
|
|
2668
|
+
const existingSnapshot = existingSnapshotResult.recordset[0].snapshot;
|
|
2669
|
+
snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
|
|
2670
|
+
}
|
|
2671
|
+
snapshot.context[stepId] = result;
|
|
2672
|
+
snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
|
|
2673
|
+
const upsertReq = new sql2.Request(transaction);
|
|
2674
|
+
upsertReq.input("workflow_name", workflowName);
|
|
2675
|
+
upsertReq.input("run_id", runId);
|
|
2676
|
+
upsertReq.input("snapshot", JSON.stringify(snapshot));
|
|
2677
|
+
upsertReq.input("createdAt", sql2.DateTime2, /* @__PURE__ */ new Date());
|
|
2678
|
+
upsertReq.input("updatedAt", sql2.DateTime2, /* @__PURE__ */ new Date());
|
|
2679
|
+
await upsertReq.query(
|
|
2680
|
+
`MERGE ${table} AS target
|
|
2681
|
+
USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
|
|
2682
|
+
ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
|
|
2683
|
+
WHEN MATCHED THEN UPDATE SET snapshot = @snapshot, [updatedAt] = @updatedAt
|
|
2684
|
+
WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, snapshot, [createdAt], [updatedAt])
|
|
2685
|
+
VALUES (@workflow_name, @run_id, @snapshot, @createdAt, @updatedAt);`
|
|
2686
|
+
);
|
|
2687
|
+
await transaction.commit();
|
|
2688
|
+
return snapshot.context;
|
|
2689
|
+
} catch (error) {
|
|
2690
|
+
try {
|
|
2691
|
+
await transaction.rollback();
|
|
2692
|
+
} catch {
|
|
2693
|
+
}
|
|
2694
|
+
throw new MastraError(
|
|
2695
|
+
{
|
|
2696
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_RESULTS_FAILED",
|
|
2697
|
+
domain: ErrorDomain.STORAGE,
|
|
2698
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2699
|
+
details: {
|
|
2700
|
+
workflowName,
|
|
2701
|
+
runId,
|
|
2702
|
+
stepId
|
|
2703
|
+
}
|
|
2704
|
+
},
|
|
2705
|
+
error
|
|
2706
|
+
);
|
|
2707
|
+
}
|
|
1783
2708
|
}
|
|
1784
|
-
updateWorkflowState({
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
2709
|
+
async updateWorkflowState({
|
|
2710
|
+
workflowName,
|
|
2711
|
+
runId,
|
|
2712
|
+
opts
|
|
1788
2713
|
}) {
|
|
1789
|
-
|
|
2714
|
+
const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
2715
|
+
const transaction = this.pool.transaction();
|
|
2716
|
+
try {
|
|
2717
|
+
await transaction.begin();
|
|
2718
|
+
const selectRequest = new sql2.Request(transaction);
|
|
2719
|
+
selectRequest.input("workflow_name", workflowName);
|
|
2720
|
+
selectRequest.input("run_id", runId);
|
|
2721
|
+
const existingSnapshotResult = await selectRequest.query(
|
|
2722
|
+
`SELECT snapshot FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
2723
|
+
);
|
|
2724
|
+
if (!existingSnapshotResult.recordset || existingSnapshotResult.recordset.length === 0) {
|
|
2725
|
+
await transaction.rollback();
|
|
2726
|
+
return void 0;
|
|
2727
|
+
}
|
|
2728
|
+
const existingSnapshot = existingSnapshotResult.recordset[0].snapshot;
|
|
2729
|
+
const snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
|
|
2730
|
+
if (!snapshot || !snapshot?.context) {
|
|
2731
|
+
await transaction.rollback();
|
|
2732
|
+
throw new MastraError(
|
|
2733
|
+
{
|
|
2734
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_SNAPSHOT_NOT_FOUND",
|
|
2735
|
+
domain: ErrorDomain.STORAGE,
|
|
2736
|
+
category: ErrorCategory.SYSTEM,
|
|
2737
|
+
details: {
|
|
2738
|
+
workflowName,
|
|
2739
|
+
runId
|
|
2740
|
+
}
|
|
2741
|
+
},
|
|
2742
|
+
new Error(`Snapshot not found for runId ${runId}`)
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
const updatedSnapshot = { ...snapshot, ...opts };
|
|
2746
|
+
const updateRequest = new sql2.Request(transaction);
|
|
2747
|
+
updateRequest.input("snapshot", JSON.stringify(updatedSnapshot));
|
|
2748
|
+
updateRequest.input("workflow_name", workflowName);
|
|
2749
|
+
updateRequest.input("run_id", runId);
|
|
2750
|
+
updateRequest.input("updatedAt", sql2.DateTime2, /* @__PURE__ */ new Date());
|
|
2751
|
+
await updateRequest.query(
|
|
2752
|
+
`UPDATE ${table} SET snapshot = @snapshot, [updatedAt] = @updatedAt WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
2753
|
+
);
|
|
2754
|
+
await transaction.commit();
|
|
2755
|
+
return updatedSnapshot;
|
|
2756
|
+
} catch (error) {
|
|
2757
|
+
try {
|
|
2758
|
+
await transaction.rollback();
|
|
2759
|
+
} catch {
|
|
2760
|
+
}
|
|
2761
|
+
throw new MastraError(
|
|
2762
|
+
{
|
|
2763
|
+
id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_FAILED",
|
|
2764
|
+
domain: ErrorDomain.STORAGE,
|
|
2765
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2766
|
+
details: {
|
|
2767
|
+
workflowName,
|
|
2768
|
+
runId
|
|
2769
|
+
}
|
|
2770
|
+
},
|
|
2771
|
+
error
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
1790
2774
|
}
|
|
1791
2775
|
async persistWorkflowSnapshot({
|
|
1792
2776
|
workflowName,
|
|
@@ -1884,7 +2868,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1884
2868
|
if (!result.recordset || result.recordset.length === 0) {
|
|
1885
2869
|
return null;
|
|
1886
2870
|
}
|
|
1887
|
-
return parseWorkflowRun(result.recordset[0]);
|
|
2871
|
+
return this.parseWorkflowRun(result.recordset[0]);
|
|
1888
2872
|
} catch (error) {
|
|
1889
2873
|
throw new MastraError(
|
|
1890
2874
|
{
|
|
@@ -1921,7 +2905,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1921
2905
|
conditions.push(`[resourceId] = @resourceId`);
|
|
1922
2906
|
paramMap["resourceId"] = resourceId;
|
|
1923
2907
|
} else {
|
|
1924
|
-
|
|
2908
|
+
this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
1925
2909
|
}
|
|
1926
2910
|
}
|
|
1927
2911
|
if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
|
|
@@ -1955,7 +2939,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1955
2939
|
request.input("offset", offset);
|
|
1956
2940
|
}
|
|
1957
2941
|
const result = await request.query(query);
|
|
1958
|
-
const runs = (result.recordset || []).map((row) => parseWorkflowRun(row));
|
|
2942
|
+
const runs = (result.recordset || []).map((row) => this.parseWorkflowRun(row));
|
|
1959
2943
|
return { runs, total: total || runs.length };
|
|
1960
2944
|
} catch (error) {
|
|
1961
2945
|
throw new MastraError(
|
|
@@ -1971,6 +2955,9 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1971
2955
|
);
|
|
1972
2956
|
}
|
|
1973
2957
|
}
|
|
2958
|
+
async listWorkflowRuns(args) {
|
|
2959
|
+
return this.getWorkflowRuns(args);
|
|
2960
|
+
}
|
|
1974
2961
|
};
|
|
1975
2962
|
|
|
1976
2963
|
// src/storage/index.ts
|
|
@@ -2003,17 +2990,17 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2003
2990
|
port: config.port,
|
|
2004
2991
|
options: config.options || { encrypt: true, trustServerCertificate: true }
|
|
2005
2992
|
});
|
|
2006
|
-
const legacyEvals = new LegacyEvalsMSSQL({ pool: this.pool, schema: this.schema });
|
|
2007
2993
|
const operations = new StoreOperationsMSSQL({ pool: this.pool, schemaName: this.schema });
|
|
2008
2994
|
const scores = new ScoresMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2009
2995
|
const workflows = new WorkflowsMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2010
2996
|
const memory = new MemoryMSSQL({ pool: this.pool, schema: this.schema, operations });
|
|
2997
|
+
const observability = new ObservabilityMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2011
2998
|
this.stores = {
|
|
2012
2999
|
operations,
|
|
2013
3000
|
scores,
|
|
2014
3001
|
workflows,
|
|
2015
|
-
|
|
2016
|
-
|
|
3002
|
+
memory,
|
|
3003
|
+
observability
|
|
2017
3004
|
};
|
|
2018
3005
|
} catch (e) {
|
|
2019
3006
|
throw new MastraError(
|
|
@@ -2033,6 +3020,11 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2033
3020
|
try {
|
|
2034
3021
|
await this.isConnected;
|
|
2035
3022
|
await super.init();
|
|
3023
|
+
try {
|
|
3024
|
+
await this.stores.operations.createAutomaticIndexes();
|
|
3025
|
+
} catch (indexError) {
|
|
3026
|
+
this.logger?.warn?.("Failed to create indexes:", indexError);
|
|
3027
|
+
}
|
|
2036
3028
|
} catch (error) {
|
|
2037
3029
|
this.isConnected = null;
|
|
2038
3030
|
throw new MastraError(
|
|
@@ -2060,16 +3052,11 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2060
3052
|
hasColumn: true,
|
|
2061
3053
|
createTable: true,
|
|
2062
3054
|
deleteMessages: true,
|
|
2063
|
-
getScoresBySpan: true
|
|
3055
|
+
getScoresBySpan: true,
|
|
3056
|
+
aiTracing: true,
|
|
3057
|
+
indexManagement: true
|
|
2064
3058
|
};
|
|
2065
3059
|
}
|
|
2066
|
-
/** @deprecated use getEvals instead */
|
|
2067
|
-
async getEvalsByAgentName(agentName, type) {
|
|
2068
|
-
return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
|
|
2069
|
-
}
|
|
2070
|
-
async getEvals(options = {}) {
|
|
2071
|
-
return this.stores.legacyEvals.getEvals(options);
|
|
2072
|
-
}
|
|
2073
3060
|
async createTable({
|
|
2074
3061
|
tableName,
|
|
2075
3062
|
schema
|
|
@@ -2170,9 +3157,9 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2170
3157
|
runId,
|
|
2171
3158
|
stepId,
|
|
2172
3159
|
result,
|
|
2173
|
-
|
|
3160
|
+
requestContext
|
|
2174
3161
|
}) {
|
|
2175
|
-
return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result,
|
|
3162
|
+
return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
|
|
2176
3163
|
}
|
|
2177
3164
|
async updateWorkflowState({
|
|
2178
3165
|
workflowName,
|
|
@@ -2214,6 +3201,60 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2214
3201
|
async close() {
|
|
2215
3202
|
await this.pool.close();
|
|
2216
3203
|
}
|
|
3204
|
+
/**
|
|
3205
|
+
* Index Management
|
|
3206
|
+
*/
|
|
3207
|
+
async createIndex(options) {
|
|
3208
|
+
return this.stores.operations.createIndex(options);
|
|
3209
|
+
}
|
|
3210
|
+
async listIndexes(tableName) {
|
|
3211
|
+
return this.stores.operations.listIndexes(tableName);
|
|
3212
|
+
}
|
|
3213
|
+
async describeIndex(indexName) {
|
|
3214
|
+
return this.stores.operations.describeIndex(indexName);
|
|
3215
|
+
}
|
|
3216
|
+
async dropIndex(indexName) {
|
|
3217
|
+
return this.stores.operations.dropIndex(indexName);
|
|
3218
|
+
}
|
|
3219
|
+
/**
|
|
3220
|
+
* AI Tracing / Observability
|
|
3221
|
+
*/
|
|
3222
|
+
getObservabilityStore() {
|
|
3223
|
+
if (!this.stores.observability) {
|
|
3224
|
+
throw new MastraError({
|
|
3225
|
+
id: "MSSQL_STORE_OBSERVABILITY_NOT_INITIALIZED",
|
|
3226
|
+
domain: ErrorDomain.STORAGE,
|
|
3227
|
+
category: ErrorCategory.SYSTEM,
|
|
3228
|
+
text: "Observability storage is not initialized"
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
return this.stores.observability;
|
|
3232
|
+
}
|
|
3233
|
+
async createAISpan(span) {
|
|
3234
|
+
return this.getObservabilityStore().createAISpan(span);
|
|
3235
|
+
}
|
|
3236
|
+
async updateAISpan({
|
|
3237
|
+
spanId,
|
|
3238
|
+
traceId,
|
|
3239
|
+
updates
|
|
3240
|
+
}) {
|
|
3241
|
+
return this.getObservabilityStore().updateAISpan({ spanId, traceId, updates });
|
|
3242
|
+
}
|
|
3243
|
+
async getAITrace(traceId) {
|
|
3244
|
+
return this.getObservabilityStore().getAITrace(traceId);
|
|
3245
|
+
}
|
|
3246
|
+
async getAITracesPaginated(args) {
|
|
3247
|
+
return this.getObservabilityStore().getAITracesPaginated(args);
|
|
3248
|
+
}
|
|
3249
|
+
async batchCreateAISpans(args) {
|
|
3250
|
+
return this.getObservabilityStore().batchCreateAISpans(args);
|
|
3251
|
+
}
|
|
3252
|
+
async batchUpdateAISpans(args) {
|
|
3253
|
+
return this.getObservabilityStore().batchUpdateAISpans(args);
|
|
3254
|
+
}
|
|
3255
|
+
async batchDeleteAITraces(args) {
|
|
3256
|
+
return this.getObservabilityStore().batchDeleteAITraces(args);
|
|
3257
|
+
}
|
|
2217
3258
|
/**
|
|
2218
3259
|
* Scorers
|
|
2219
3260
|
*/
|
|
@@ -2222,9 +3263,18 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2222
3263
|
}
|
|
2223
3264
|
async getScoresByScorerId({
|
|
2224
3265
|
scorerId: _scorerId,
|
|
2225
|
-
pagination: _pagination
|
|
3266
|
+
pagination: _pagination,
|
|
3267
|
+
entityId: _entityId,
|
|
3268
|
+
entityType: _entityType,
|
|
3269
|
+
source: _source
|
|
2226
3270
|
}) {
|
|
2227
|
-
return this.stores.scores.getScoresByScorerId({
|
|
3271
|
+
return this.stores.scores.getScoresByScorerId({
|
|
3272
|
+
scorerId: _scorerId,
|
|
3273
|
+
pagination: _pagination,
|
|
3274
|
+
entityId: _entityId,
|
|
3275
|
+
entityType: _entityType,
|
|
3276
|
+
source: _source
|
|
3277
|
+
});
|
|
2228
3278
|
}
|
|
2229
3279
|
async saveScore(_score) {
|
|
2230
3280
|
return this.stores.scores.saveScore(_score);
|