@mastra/mssql 0.0.0-toolOptionTypes-20250917085558 → 0.0.0-top-level-fix-20251211111608
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 +797 -3
- package/README.md +324 -37
- package/dist/index.cjs +1786 -776
- 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 +1787 -777
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +14 -43
- 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 +14 -5
- 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 +11 -12
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +96 -81
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +14 -9
- 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/storage/domains/traces/index.d.ts +0 -37
- package/dist/storage/domains/traces/index.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
2
|
-
import { MastraStorage,
|
|
3
|
-
import
|
|
4
|
-
import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
|
|
2
|
+
import { MastraStorage, createStorageErrorId, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_TRACES, TABLE_SCORERS, TABLE_SPANS, ScoresStorage, normalizePerPage, calculatePagination, WorkflowsStorage, MemoryStorage, TABLE_RESOURCES, ObservabilityStorage, transformScoreRow as transformScoreRow$1 } from '@mastra/core/storage';
|
|
3
|
+
import sql3 from 'mssql';
|
|
5
4
|
import { MessageList } from '@mastra/core/agent';
|
|
5
|
+
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
7
|
+
import { saveScorePayloadSchema } from '@mastra/core/evals';
|
|
6
8
|
|
|
7
9
|
// src/storage/index.ts
|
|
8
10
|
function getSchemaName(schema) {
|
|
@@ -14,154 +16,109 @@ function getTableName({ indexName, schemaName }) {
|
|
|
14
16
|
const quotedSchemaName = schemaName;
|
|
15
17
|
return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (row.test_info) {
|
|
22
|
-
try {
|
|
23
|
-
testInfoValue = typeof row.test_info === "string" ? JSON.parse(row.test_info) : row.test_info;
|
|
24
|
-
} catch {
|
|
25
|
-
}
|
|
19
|
+
function buildDateRangeFilter(dateRange, fieldName) {
|
|
20
|
+
const filters = {};
|
|
21
|
+
if (dateRange?.start) {
|
|
22
|
+
filters[`${fieldName}_gte`] = dateRange.start;
|
|
26
23
|
}
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
resultValue = typeof row.result === "string" ? JSON.parse(row.result) : row.result;
|
|
30
|
-
} catch {
|
|
31
|
-
}
|
|
24
|
+
if (dateRange?.end) {
|
|
25
|
+
filters[`${fieldName}_lte`] = dateRange.end;
|
|
32
26
|
}
|
|
33
|
-
return
|
|
34
|
-
agentName: row.agent_name,
|
|
35
|
-
input: row.input,
|
|
36
|
-
output: row.output,
|
|
37
|
-
result: resultValue,
|
|
38
|
-
metricName: row.metric_name,
|
|
39
|
-
instructions: row.instructions,
|
|
40
|
-
testInfo: testInfoValue,
|
|
41
|
-
globalRunId: row.global_run_id,
|
|
42
|
-
runId: row.run_id,
|
|
43
|
-
createdAt: row.created_at
|
|
44
|
-
};
|
|
27
|
+
return filters;
|
|
45
28
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const where = [];
|
|
82
|
-
const params = {};
|
|
83
|
-
if (agentName) {
|
|
84
|
-
where.push("agent_name = @agentName");
|
|
85
|
-
params["agentName"] = agentName;
|
|
86
|
-
}
|
|
87
|
-
if (type === "test") {
|
|
88
|
-
where.push("test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL");
|
|
89
|
-
} else if (type === "live") {
|
|
90
|
-
where.push("(test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)");
|
|
91
|
-
}
|
|
92
|
-
if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
|
|
93
|
-
where.push(`[created_at] >= @fromDate`);
|
|
94
|
-
params[`fromDate`] = fromDate.toISOString();
|
|
95
|
-
}
|
|
96
|
-
if (toDate instanceof Date && !isNaN(toDate.getTime())) {
|
|
97
|
-
where.push(`[created_at] <= @toDate`);
|
|
98
|
-
params[`toDate`] = toDate.toISOString();
|
|
99
|
-
}
|
|
100
|
-
const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
101
|
-
const tableName = getTableName({ indexName: TABLE_EVALS, schemaName: getSchemaName(this.schema) });
|
|
102
|
-
const offset = page * perPage;
|
|
103
|
-
const countQuery = `SELECT COUNT(*) as total FROM ${tableName} ${whereClause}`;
|
|
104
|
-
const dataQuery = `SELECT * FROM ${tableName} ${whereClause} ORDER BY seq_id DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
105
|
-
try {
|
|
106
|
-
const countReq = this.pool.request();
|
|
107
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
108
|
-
if (value instanceof Date) {
|
|
109
|
-
countReq.input(key, sql2.DateTime, value);
|
|
110
|
-
} else {
|
|
111
|
-
countReq.input(key, value);
|
|
29
|
+
function isInOperator(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && "$in" in value && Array.isArray(value.$in);
|
|
31
|
+
}
|
|
32
|
+
function prepareWhereClause(filters, _schema) {
|
|
33
|
+
const conditions = [];
|
|
34
|
+
const params = {};
|
|
35
|
+
let paramIndex = 1;
|
|
36
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
37
|
+
if (value === void 0) return;
|
|
38
|
+
if (key.endsWith("_gte")) {
|
|
39
|
+
const paramName = `p${paramIndex++}`;
|
|
40
|
+
const fieldName = key.slice(0, -4);
|
|
41
|
+
conditions.push(`[${parseSqlIdentifier(fieldName, "field name")}] >= @${paramName}`);
|
|
42
|
+
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
43
|
+
} else if (key.endsWith("_lte")) {
|
|
44
|
+
const paramName = `p${paramIndex++}`;
|
|
45
|
+
const fieldName = key.slice(0, -4);
|
|
46
|
+
conditions.push(`[${parseSqlIdentifier(fieldName, "field name")}] <= @${paramName}`);
|
|
47
|
+
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
48
|
+
} else if (value === null) {
|
|
49
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] IS NULL`);
|
|
50
|
+
} else if (isInOperator(value)) {
|
|
51
|
+
const inValues = value.$in;
|
|
52
|
+
if (inValues.length === 0) {
|
|
53
|
+
conditions.push("1 = 0");
|
|
54
|
+
} else if (inValues.length === 1) {
|
|
55
|
+
const paramName = `p${paramIndex++}`;
|
|
56
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] = @${paramName}`);
|
|
57
|
+
params[paramName] = inValues[0] instanceof Date ? inValues[0].toISOString() : inValues[0];
|
|
58
|
+
} else {
|
|
59
|
+
const inParamNames = [];
|
|
60
|
+
for (const item of inValues) {
|
|
61
|
+
const paramName = `p${paramIndex++}`;
|
|
62
|
+
inParamNames.push(`@${paramName}`);
|
|
63
|
+
params[paramName] = item instanceof Date ? item.toISOString() : item;
|
|
112
64
|
}
|
|
113
|
-
|
|
114
|
-
const countResult = await countReq.query(countQuery);
|
|
115
|
-
const total = countResult.recordset[0]?.total || 0;
|
|
116
|
-
if (total === 0) {
|
|
117
|
-
return {
|
|
118
|
-
evals: [],
|
|
119
|
-
total: 0,
|
|
120
|
-
page,
|
|
121
|
-
perPage,
|
|
122
|
-
hasMore: false
|
|
123
|
-
};
|
|
65
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] IN (${inParamNames.join(", ")})`);
|
|
124
66
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
67
|
+
} else if (Array.isArray(value)) {
|
|
68
|
+
if (value.length === 0) {
|
|
69
|
+
conditions.push("1 = 0");
|
|
70
|
+
} else if (value.length === 1) {
|
|
71
|
+
const paramName = `p${paramIndex++}`;
|
|
72
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] = @${paramName}`);
|
|
73
|
+
params[paramName] = value[0] instanceof Date ? value[0].toISOString() : value[0];
|
|
74
|
+
} else {
|
|
75
|
+
const inParamNames = [];
|
|
76
|
+
for (const item of value) {
|
|
77
|
+
const paramName = `p${paramIndex++}`;
|
|
78
|
+
inParamNames.push(`@${paramName}`);
|
|
79
|
+
params[paramName] = item instanceof Date ? item.toISOString() : item;
|
|
131
80
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
evals: rows?.map((row) => transformEvalRow(row)) ?? [],
|
|
139
|
-
total,
|
|
140
|
-
page,
|
|
141
|
-
perPage,
|
|
142
|
-
hasMore: offset + (rows?.length ?? 0) < total
|
|
143
|
-
};
|
|
144
|
-
} catch (error) {
|
|
145
|
-
const mastraError = new MastraError(
|
|
146
|
-
{
|
|
147
|
-
id: "MASTRA_STORAGE_MSSQL_STORE_GET_EVALS_FAILED",
|
|
148
|
-
domain: ErrorDomain.STORAGE,
|
|
149
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
150
|
-
details: {
|
|
151
|
-
agentName: agentName || "all",
|
|
152
|
-
type: type || "all",
|
|
153
|
-
page,
|
|
154
|
-
perPage
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
error
|
|
158
|
-
);
|
|
159
|
-
this.logger?.error?.(mastraError.toString());
|
|
160
|
-
this.logger?.trackException(mastraError);
|
|
161
|
-
throw mastraError;
|
|
81
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] IN (${inParamNames.join(", ")})`);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
const paramName = `p${paramIndex++}`;
|
|
85
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] = @${paramName}`);
|
|
86
|
+
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
162
87
|
}
|
|
163
|
-
}
|
|
164
|
-
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
|
|
91
|
+
params
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function transformFromSqlRow({
|
|
95
|
+
tableName,
|
|
96
|
+
sqlRow
|
|
97
|
+
}) {
|
|
98
|
+
const schema = TABLE_SCHEMAS[tableName];
|
|
99
|
+
const result = {};
|
|
100
|
+
Object.entries(sqlRow).forEach(([key, value]) => {
|
|
101
|
+
const columnSchema = schema?.[key];
|
|
102
|
+
if (columnSchema?.type === "jsonb" && typeof value === "string") {
|
|
103
|
+
try {
|
|
104
|
+
result[key] = JSON.parse(value);
|
|
105
|
+
} catch {
|
|
106
|
+
result[key] = value;
|
|
107
|
+
}
|
|
108
|
+
} else if (columnSchema?.type === "timestamp" && value && typeof value === "string") {
|
|
109
|
+
result[key] = new Date(value);
|
|
110
|
+
} else if (columnSchema?.type === "timestamp" && value instanceof Date) {
|
|
111
|
+
result[key] = value;
|
|
112
|
+
} else if (columnSchema?.type === "boolean") {
|
|
113
|
+
result[key] = Boolean(value);
|
|
114
|
+
} else {
|
|
115
|
+
result[key] = value;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/storage/domains/memory/index.ts
|
|
165
122
|
var MemoryMSSQL = class extends MemoryStorage {
|
|
166
123
|
pool;
|
|
167
124
|
schema;
|
|
@@ -179,7 +136,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
179
136
|
});
|
|
180
137
|
const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
|
|
181
138
|
const list = new MessageList().add(cleanMessages, "memory");
|
|
182
|
-
return format === "v2" ? list.get.all.
|
|
139
|
+
return format === "v2" ? list.get.all.db() : list.get.all.v1();
|
|
183
140
|
}
|
|
184
141
|
constructor({
|
|
185
142
|
pool,
|
|
@@ -193,7 +150,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
193
150
|
}
|
|
194
151
|
async getThreadById({ threadId }) {
|
|
195
152
|
try {
|
|
196
|
-
const
|
|
153
|
+
const sql5 = `SELECT
|
|
197
154
|
id,
|
|
198
155
|
[resourceId],
|
|
199
156
|
title,
|
|
@@ -204,7 +161,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
204
161
|
WHERE id = @threadId`;
|
|
205
162
|
const request = this.pool.request();
|
|
206
163
|
request.input("threadId", threadId);
|
|
207
|
-
const resultSet = await request.query(
|
|
164
|
+
const resultSet = await request.query(sql5);
|
|
208
165
|
const thread = resultSet.recordset[0] || null;
|
|
209
166
|
if (!thread) {
|
|
210
167
|
return null;
|
|
@@ -218,7 +175,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
218
175
|
} catch (error) {
|
|
219
176
|
throw new MastraError(
|
|
220
177
|
{
|
|
221
|
-
id: "
|
|
178
|
+
id: createStorageErrorId("MSSQL", "GET_THREAD_BY_ID", "FAILED"),
|
|
222
179
|
domain: ErrorDomain.STORAGE,
|
|
223
180
|
category: ErrorCategory.THIRD_PARTY,
|
|
224
181
|
details: {
|
|
@@ -229,11 +186,24 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
229
186
|
);
|
|
230
187
|
}
|
|
231
188
|
}
|
|
232
|
-
async
|
|
233
|
-
const { resourceId, page = 0, perPage: perPageInput, orderBy
|
|
189
|
+
async listThreadsByResourceId(args) {
|
|
190
|
+
const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
|
|
191
|
+
if (page < 0) {
|
|
192
|
+
throw new MastraError({
|
|
193
|
+
id: createStorageErrorId("MSSQL", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
|
|
194
|
+
domain: ErrorDomain.STORAGE,
|
|
195
|
+
category: ErrorCategory.USER,
|
|
196
|
+
text: "Page number must be non-negative",
|
|
197
|
+
details: {
|
|
198
|
+
resourceId,
|
|
199
|
+
page
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
204
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
205
|
+
const { field, direction } = this.parseOrderBy(orderBy);
|
|
234
206
|
try {
|
|
235
|
-
const perPage = perPageInput !== void 0 ? perPageInput : 100;
|
|
236
|
-
const currentOffset = page * perPage;
|
|
237
207
|
const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
|
|
238
208
|
const countQuery = `SELECT COUNT(*) as count ${baseQuery}`;
|
|
239
209
|
const countRequest = this.pool.request();
|
|
@@ -245,16 +215,22 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
245
215
|
threads: [],
|
|
246
216
|
total: 0,
|
|
247
217
|
page,
|
|
248
|
-
perPage,
|
|
218
|
+
perPage: perPageForResponse,
|
|
249
219
|
hasMore: false
|
|
250
220
|
};
|
|
251
221
|
}
|
|
252
|
-
const orderByField =
|
|
253
|
-
const
|
|
222
|
+
const orderByField = field === "createdAt" ? "[createdAt]" : "[updatedAt]";
|
|
223
|
+
const dir = (direction || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
|
|
224
|
+
const limitValue = perPageInput === false ? total : perPage;
|
|
225
|
+
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
254
226
|
const dataRequest = this.pool.request();
|
|
255
227
|
dataRequest.input("resourceId", resourceId);
|
|
256
|
-
dataRequest.input("
|
|
257
|
-
|
|
228
|
+
dataRequest.input("offset", offset);
|
|
229
|
+
if (limitValue > 2147483647) {
|
|
230
|
+
dataRequest.input("perPage", sql3.BigInt, limitValue);
|
|
231
|
+
} else {
|
|
232
|
+
dataRequest.input("perPage", limitValue);
|
|
233
|
+
}
|
|
258
234
|
const rowsResult = await dataRequest.query(dataQuery);
|
|
259
235
|
const rows = rowsResult.recordset || [];
|
|
260
236
|
const threads = rows.map((thread) => ({
|
|
@@ -267,13 +243,13 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
267
243
|
threads,
|
|
268
244
|
total,
|
|
269
245
|
page,
|
|
270
|
-
perPage,
|
|
271
|
-
hasMore:
|
|
246
|
+
perPage: perPageForResponse,
|
|
247
|
+
hasMore: perPageInput === false ? false : offset + perPage < total
|
|
272
248
|
};
|
|
273
249
|
} catch (error) {
|
|
274
250
|
const mastraError = new MastraError(
|
|
275
251
|
{
|
|
276
|
-
id: "
|
|
252
|
+
id: createStorageErrorId("MSSQL", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
|
|
277
253
|
domain: ErrorDomain.STORAGE,
|
|
278
254
|
category: ErrorCategory.THIRD_PARTY,
|
|
279
255
|
details: {
|
|
@@ -285,7 +261,13 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
285
261
|
);
|
|
286
262
|
this.logger?.error?.(mastraError.toString());
|
|
287
263
|
this.logger?.trackException?.(mastraError);
|
|
288
|
-
return {
|
|
264
|
+
return {
|
|
265
|
+
threads: [],
|
|
266
|
+
total: 0,
|
|
267
|
+
page,
|
|
268
|
+
perPage: perPageForResponse,
|
|
269
|
+
hasMore: false
|
|
270
|
+
};
|
|
289
271
|
}
|
|
290
272
|
}
|
|
291
273
|
async saveThread({ thread }) {
|
|
@@ -307,15 +289,20 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
307
289
|
req.input("id", thread.id);
|
|
308
290
|
req.input("resourceId", thread.resourceId);
|
|
309
291
|
req.input("title", thread.title);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
292
|
+
const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
|
|
293
|
+
if (metadata === null) {
|
|
294
|
+
req.input("metadata", sql3.NVarChar, null);
|
|
295
|
+
} else {
|
|
296
|
+
req.input("metadata", metadata);
|
|
297
|
+
}
|
|
298
|
+
req.input("createdAt", sql3.DateTime2, thread.createdAt);
|
|
299
|
+
req.input("updatedAt", sql3.DateTime2, thread.updatedAt);
|
|
313
300
|
await req.query(mergeSql);
|
|
314
301
|
return thread;
|
|
315
302
|
} catch (error) {
|
|
316
303
|
throw new MastraError(
|
|
317
304
|
{
|
|
318
|
-
id: "
|
|
305
|
+
id: createStorageErrorId("MSSQL", "SAVE_THREAD", "FAILED"),
|
|
319
306
|
domain: ErrorDomain.STORAGE,
|
|
320
307
|
category: ErrorCategory.THIRD_PARTY,
|
|
321
308
|
details: {
|
|
@@ -326,30 +313,6 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
326
313
|
);
|
|
327
314
|
}
|
|
328
315
|
}
|
|
329
|
-
/**
|
|
330
|
-
* @deprecated use getThreadsByResourceIdPaginated instead
|
|
331
|
-
*/
|
|
332
|
-
async getThreadsByResourceId(args) {
|
|
333
|
-
const { resourceId, orderBy = "createdAt", sortDirection = "DESC" } = args;
|
|
334
|
-
try {
|
|
335
|
-
const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
|
|
336
|
-
const orderByField = orderBy === "createdAt" ? "[createdAt]" : "[updatedAt]";
|
|
337
|
-
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${sortDirection}`;
|
|
338
|
-
const request = this.pool.request();
|
|
339
|
-
request.input("resourceId", resourceId);
|
|
340
|
-
const resultSet = await request.query(dataQuery);
|
|
341
|
-
const rows = resultSet.recordset || [];
|
|
342
|
-
return rows.map((thread) => ({
|
|
343
|
-
...thread,
|
|
344
|
-
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
345
|
-
createdAt: thread.createdAt,
|
|
346
|
-
updatedAt: thread.updatedAt
|
|
347
|
-
}));
|
|
348
|
-
} catch (error) {
|
|
349
|
-
this.logger?.error?.(`Error getting threads for resource ${resourceId}:`, error);
|
|
350
|
-
return [];
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
316
|
/**
|
|
354
317
|
* Updates a thread's title and metadata, merging with existing metadata. Returns the updated thread.
|
|
355
318
|
*/
|
|
@@ -361,7 +324,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
361
324
|
const existingThread = await this.getThreadById({ threadId: id });
|
|
362
325
|
if (!existingThread) {
|
|
363
326
|
throw new MastraError({
|
|
364
|
-
id: "
|
|
327
|
+
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
365
328
|
domain: ErrorDomain.STORAGE,
|
|
366
329
|
category: ErrorCategory.USER,
|
|
367
330
|
text: `Thread ${id} not found`,
|
|
@@ -377,7 +340,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
377
340
|
};
|
|
378
341
|
try {
|
|
379
342
|
const table = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
|
|
380
|
-
const
|
|
343
|
+
const sql5 = `UPDATE ${table}
|
|
381
344
|
SET title = @title,
|
|
382
345
|
metadata = @metadata,
|
|
383
346
|
[updatedAt] = @updatedAt
|
|
@@ -388,7 +351,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
388
351
|
req.input("title", title);
|
|
389
352
|
req.input("metadata", JSON.stringify(mergedMetadata));
|
|
390
353
|
req.input("updatedAt", /* @__PURE__ */ new Date());
|
|
391
|
-
const result = await req.query(
|
|
354
|
+
const result = await req.query(sql5);
|
|
392
355
|
let thread = result.recordset && result.recordset[0];
|
|
393
356
|
if (thread && "seq_id" in thread) {
|
|
394
357
|
const { seq_id, ...rest } = thread;
|
|
@@ -396,7 +359,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
396
359
|
}
|
|
397
360
|
if (!thread) {
|
|
398
361
|
throw new MastraError({
|
|
399
|
-
id: "
|
|
362
|
+
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
400
363
|
domain: ErrorDomain.STORAGE,
|
|
401
364
|
category: ErrorCategory.USER,
|
|
402
365
|
text: `Thread ${id} not found after update`,
|
|
@@ -415,7 +378,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
415
378
|
} catch (error) {
|
|
416
379
|
throw new MastraError(
|
|
417
380
|
{
|
|
418
|
-
id: "
|
|
381
|
+
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "FAILED"),
|
|
419
382
|
domain: ErrorDomain.STORAGE,
|
|
420
383
|
category: ErrorCategory.THIRD_PARTY,
|
|
421
384
|
details: {
|
|
@@ -445,7 +408,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
445
408
|
});
|
|
446
409
|
throw new MastraError(
|
|
447
410
|
{
|
|
448
|
-
id: "
|
|
411
|
+
id: createStorageErrorId("MSSQL", "DELETE_THREAD", "FAILED"),
|
|
449
412
|
domain: ErrorDomain.STORAGE,
|
|
450
413
|
category: ErrorCategory.THIRD_PARTY,
|
|
451
414
|
details: {
|
|
@@ -456,25 +419,18 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
456
419
|
);
|
|
457
420
|
}
|
|
458
421
|
}
|
|
459
|
-
async _getIncludedMessages({
|
|
460
|
-
|
|
461
|
-
selectBy,
|
|
462
|
-
orderByStatement
|
|
463
|
-
}) {
|
|
464
|
-
if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
|
|
465
|
-
const include = selectBy?.include;
|
|
466
|
-
if (!include) return null;
|
|
422
|
+
async _getIncludedMessages({ include }) {
|
|
423
|
+
if (!include || include.length === 0) return null;
|
|
467
424
|
const unionQueries = [];
|
|
468
425
|
const paramValues = [];
|
|
469
426
|
let paramIdx = 1;
|
|
470
427
|
const paramNames = [];
|
|
428
|
+
const tableName = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
|
|
471
429
|
for (const inc of include) {
|
|
472
430
|
const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
|
|
473
|
-
const
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
const pPrev = `@p${paramIdx + 2}`;
|
|
477
|
-
const pNext = `@p${paramIdx + 3}`;
|
|
431
|
+
const pId = `@p${paramIdx}`;
|
|
432
|
+
const pPrev = `@p${paramIdx + 1}`;
|
|
433
|
+
const pNext = `@p${paramIdx + 2}`;
|
|
478
434
|
unionQueries.push(
|
|
479
435
|
`
|
|
480
436
|
SELECT
|
|
@@ -487,30 +443,32 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
487
443
|
m.[resourceId],
|
|
488
444
|
m.seq_id
|
|
489
445
|
FROM (
|
|
490
|
-
SELECT *, ROW_NUMBER() OVER (
|
|
491
|
-
FROM ${
|
|
492
|
-
WHERE [thread_id] = ${
|
|
446
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
|
|
447
|
+
FROM ${tableName}
|
|
448
|
+
WHERE [thread_id] = (SELECT thread_id FROM ${tableName} WHERE id = ${pId})
|
|
493
449
|
) AS m
|
|
494
450
|
WHERE m.id = ${pId}
|
|
495
451
|
OR EXISTS (
|
|
496
452
|
SELECT 1
|
|
497
453
|
FROM (
|
|
498
|
-
SELECT *, ROW_NUMBER() OVER (
|
|
499
|
-
FROM ${
|
|
500
|
-
WHERE [thread_id] = ${
|
|
454
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
|
|
455
|
+
FROM ${tableName}
|
|
456
|
+
WHERE [thread_id] = (SELECT thread_id FROM ${tableName} WHERE id = ${pId})
|
|
501
457
|
) AS target
|
|
502
458
|
WHERE target.id = ${pId}
|
|
503
459
|
AND (
|
|
504
|
-
|
|
460
|
+
-- Get previous messages (messages that come BEFORE the target)
|
|
461
|
+
(m.row_num < target.row_num AND m.row_num >= target.row_num - ${pPrev})
|
|
505
462
|
OR
|
|
506
|
-
|
|
463
|
+
-- Get next messages (messages that come AFTER the target)
|
|
464
|
+
(m.row_num > target.row_num AND m.row_num <= target.row_num + ${pNext})
|
|
507
465
|
)
|
|
508
466
|
)
|
|
509
467
|
`
|
|
510
468
|
);
|
|
511
|
-
paramValues.push(
|
|
512
|
-
paramNames.push(`p${paramIdx}`, `p${paramIdx + 1}`, `p${paramIdx + 2}
|
|
513
|
-
paramIdx +=
|
|
469
|
+
paramValues.push(id, withPreviousMessages, withNextMessages);
|
|
470
|
+
paramNames.push(`p${paramIdx}`, `p${paramIdx + 1}`, `p${paramIdx + 2}`);
|
|
471
|
+
paramIdx += 3;
|
|
514
472
|
}
|
|
515
473
|
const finalQuery = `
|
|
516
474
|
SELECT * FROM (
|
|
@@ -532,34 +490,16 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
532
490
|
});
|
|
533
491
|
return dedupedRows;
|
|
534
492
|
}
|
|
535
|
-
async
|
|
536
|
-
|
|
493
|
+
async listMessagesById({ messageIds }) {
|
|
494
|
+
if (messageIds.length === 0) return { messages: [] };
|
|
537
495
|
const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
|
|
538
496
|
const orderByStatement = `ORDER BY [seq_id] DESC`;
|
|
539
|
-
const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
|
|
540
497
|
try {
|
|
541
|
-
if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
|
|
542
498
|
let rows = [];
|
|
543
|
-
|
|
544
|
-
if (include?.length) {
|
|
545
|
-
const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
|
|
546
|
-
if (includeMessages) {
|
|
547
|
-
rows.push(...includeMessages);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
const excludeIds = rows.map((m) => m.id).filter(Boolean);
|
|
551
|
-
let query = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE [thread_id] = @threadId`;
|
|
499
|
+
let query = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE [id] IN (${messageIds.map((_, i) => `@id${i}`).join(", ")})`;
|
|
552
500
|
const request = this.pool.request();
|
|
553
|
-
request.input(
|
|
554
|
-
|
|
555
|
-
const excludeParams = excludeIds.map((_, idx) => `@id${idx}`);
|
|
556
|
-
query += ` AND id NOT IN (${excludeParams.join(", ")})`;
|
|
557
|
-
excludeIds.forEach((id, idx) => {
|
|
558
|
-
request.input(`id${idx}`, id);
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
query += ` ${orderByStatement} OFFSET 0 ROWS FETCH NEXT @limit ROWS ONLY`;
|
|
562
|
-
request.input("limit", limit);
|
|
501
|
+
messageIds.forEach((id, i) => request.input(`id${i}`, id));
|
|
502
|
+
query += ` ${orderByStatement}`;
|
|
563
503
|
const result = await request.query(query);
|
|
564
504
|
const remainingRows = result.recordset || [];
|
|
565
505
|
rows.push(...remainingRows);
|
|
@@ -567,157 +507,177 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
567
507
|
const timeDiff = a.seq_id - b.seq_id;
|
|
568
508
|
return timeDiff;
|
|
569
509
|
});
|
|
570
|
-
|
|
571
|
-
|
|
510
|
+
const messagesWithParsedContent = rows.map((row) => {
|
|
511
|
+
if (typeof row.content === "string") {
|
|
512
|
+
try {
|
|
513
|
+
return { ...row, content: JSON.parse(row.content) };
|
|
514
|
+
} catch {
|
|
515
|
+
return row;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return row;
|
|
519
|
+
});
|
|
520
|
+
const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
|
|
521
|
+
const list = new MessageList().add(cleanMessages, "memory");
|
|
522
|
+
return { messages: list.get.all.db() };
|
|
572
523
|
} catch (error) {
|
|
573
524
|
const mastraError = new MastraError(
|
|
574
525
|
{
|
|
575
|
-
id: "
|
|
526
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES_BY_ID", "FAILED"),
|
|
576
527
|
domain: ErrorDomain.STORAGE,
|
|
577
528
|
category: ErrorCategory.THIRD_PARTY,
|
|
578
529
|
details: {
|
|
579
|
-
|
|
580
|
-
resourceId: resourceId ?? ""
|
|
530
|
+
messageIds: JSON.stringify(messageIds)
|
|
581
531
|
}
|
|
582
532
|
},
|
|
583
533
|
error
|
|
584
534
|
);
|
|
585
535
|
this.logger?.error?.(mastraError.toString());
|
|
586
|
-
this.logger?.trackException(mastraError);
|
|
587
|
-
return [];
|
|
536
|
+
this.logger?.trackException?.(mastraError);
|
|
537
|
+
return { messages: [] };
|
|
588
538
|
}
|
|
589
539
|
}
|
|
590
|
-
async
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
|
|
596
|
-
const orderByStatement = `ORDER BY [seq_id] DESC`;
|
|
597
|
-
try {
|
|
598
|
-
let rows = [];
|
|
599
|
-
let query = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE [id] IN (${messageIds.map((_, i) => `@id${i}`).join(", ")})`;
|
|
600
|
-
const request = this.pool.request();
|
|
601
|
-
messageIds.forEach((id, i) => request.input(`id${i}`, id));
|
|
602
|
-
query += ` ${orderByStatement}`;
|
|
603
|
-
const result = await request.query(query);
|
|
604
|
-
const remainingRows = result.recordset || [];
|
|
605
|
-
rows.push(...remainingRows);
|
|
606
|
-
rows.sort((a, b) => {
|
|
607
|
-
const timeDiff = a.seq_id - b.seq_id;
|
|
608
|
-
return timeDiff;
|
|
609
|
-
});
|
|
610
|
-
rows = rows.map(({ seq_id, ...rest }) => rest);
|
|
611
|
-
if (format === `v1`) return this._parseAndFormatMessages(rows, format);
|
|
612
|
-
return this._parseAndFormatMessages(rows, `v2`);
|
|
613
|
-
} catch (error) {
|
|
614
|
-
const mastraError = new MastraError(
|
|
540
|
+
async listMessages(args) {
|
|
541
|
+
const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
|
|
542
|
+
const threadIds = Array.isArray(threadId) ? threadId : [threadId];
|
|
543
|
+
if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
|
|
544
|
+
throw new MastraError(
|
|
615
545
|
{
|
|
616
|
-
id: "
|
|
546
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES", "INVALID_THREAD_ID"),
|
|
617
547
|
domain: ErrorDomain.STORAGE,
|
|
618
548
|
category: ErrorCategory.THIRD_PARTY,
|
|
619
|
-
details: {
|
|
620
|
-
messageIds: JSON.stringify(messageIds)
|
|
621
|
-
}
|
|
549
|
+
details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
|
|
622
550
|
},
|
|
623
|
-
|
|
551
|
+
new Error("threadId must be a non-empty string or array of non-empty strings")
|
|
624
552
|
);
|
|
625
|
-
this.logger?.error?.(mastraError.toString());
|
|
626
|
-
this.logger?.trackException(mastraError);
|
|
627
|
-
return [];
|
|
628
553
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
554
|
+
if (page < 0) {
|
|
555
|
+
throw new MastraError({
|
|
556
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES", "INVALID_PAGE"),
|
|
557
|
+
domain: ErrorDomain.STORAGE,
|
|
558
|
+
category: ErrorCategory.USER,
|
|
559
|
+
text: "Page number must be non-negative",
|
|
560
|
+
details: {
|
|
561
|
+
threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
|
|
562
|
+
page
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
const perPage = normalizePerPage(perPageInput, 40);
|
|
567
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
633
568
|
try {
|
|
634
|
-
|
|
635
|
-
const
|
|
636
|
-
const
|
|
637
|
-
const
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
request
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
request.input("fromDate", fromDate.toISOString());
|
|
652
|
-
}
|
|
653
|
-
if (toDate instanceof Date && !isNaN(toDate.getTime())) {
|
|
654
|
-
conditions.push("[createdAt] <= @toDate");
|
|
655
|
-
request.input("toDate", toDate.toISOString());
|
|
656
|
-
}
|
|
657
|
-
const whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
658
|
-
const countQuery = `SELECT COUNT(*) as total FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} ${whereClause}`;
|
|
659
|
-
const countResult = await request.query(countQuery);
|
|
569
|
+
const { field, direction } = this.parseOrderBy(orderBy, "ASC");
|
|
570
|
+
const orderByStatement = `ORDER BY [${field}] ${direction}, [seq_id] ${direction}`;
|
|
571
|
+
const tableName = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
|
|
572
|
+
const baseQuery = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId FROM ${tableName}`;
|
|
573
|
+
const filters = {
|
|
574
|
+
thread_id: threadIds.length === 1 ? threadIds[0] : { $in: threadIds },
|
|
575
|
+
...resourceId ? { resourceId } : {},
|
|
576
|
+
...buildDateRangeFilter(filter?.dateRange, "createdAt")
|
|
577
|
+
};
|
|
578
|
+
const { sql: actualWhereClause = "", params: whereParams } = prepareWhereClause(
|
|
579
|
+
filters);
|
|
580
|
+
const bindWhereParams = (req) => {
|
|
581
|
+
Object.entries(whereParams).forEach(([paramName, paramValue]) => req.input(paramName, paramValue));
|
|
582
|
+
};
|
|
583
|
+
const countRequest = this.pool.request();
|
|
584
|
+
bindWhereParams(countRequest);
|
|
585
|
+
const countResult = await countRequest.query(`SELECT COUNT(*) as total FROM ${tableName}${actualWhereClause}`);
|
|
660
586
|
const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
|
|
661
|
-
|
|
662
|
-
const
|
|
587
|
+
const fetchBaseMessages = async () => {
|
|
588
|
+
const request = this.pool.request();
|
|
589
|
+
bindWhereParams(request);
|
|
590
|
+
if (perPageInput === false) {
|
|
591
|
+
const result2 = await request.query(`${baseQuery}${actualWhereClause} ${orderByStatement}`);
|
|
592
|
+
return result2.recordset || [];
|
|
593
|
+
}
|
|
594
|
+
request.input("offset", offset);
|
|
595
|
+
request.input("limit", perPage > 2147483647 ? sql3.BigInt : sql3.Int, perPage);
|
|
596
|
+
const result = await request.query(
|
|
597
|
+
`${baseQuery}${actualWhereClause} ${orderByStatement} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
|
|
598
|
+
);
|
|
599
|
+
return result.recordset || [];
|
|
600
|
+
};
|
|
601
|
+
const baseRows = perPage === 0 ? [] : await fetchBaseMessages();
|
|
602
|
+
const messages = [...baseRows];
|
|
603
|
+
const seqById = /* @__PURE__ */ new Map();
|
|
604
|
+
messages.forEach((msg) => {
|
|
605
|
+
if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
|
|
606
|
+
});
|
|
607
|
+
if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
|
|
663
608
|
return {
|
|
664
|
-
messages:
|
|
665
|
-
total:
|
|
609
|
+
messages: [],
|
|
610
|
+
total: 0,
|
|
666
611
|
page,
|
|
667
|
-
perPage,
|
|
612
|
+
perPage: perPageForResponse,
|
|
668
613
|
hasMore: false
|
|
669
614
|
};
|
|
670
615
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
616
|
+
if (include?.length) {
|
|
617
|
+
const messageIds = new Set(messages.map((m) => m.id));
|
|
618
|
+
const includeMessages = await this._getIncludedMessages({ include });
|
|
619
|
+
includeMessages?.forEach((msg) => {
|
|
620
|
+
if (!messageIds.has(msg.id)) {
|
|
621
|
+
messages.push(msg);
|
|
622
|
+
messageIds.add(msg.id);
|
|
623
|
+
if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
const parsed = this._parseAndFormatMessages(messages, "v2");
|
|
628
|
+
const mult = direction === "ASC" ? 1 : -1;
|
|
629
|
+
const finalMessages = parsed.sort((a, b) => {
|
|
630
|
+
const aVal = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
631
|
+
const bVal = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
632
|
+
if (aVal == null || bVal == null) {
|
|
633
|
+
return aVal == null && bVal == null ? a.id.localeCompare(b.id) : aVal == null ? 1 : -1;
|
|
634
|
+
}
|
|
635
|
+
const diff = (typeof aVal === "number" && typeof bVal === "number" ? aVal - bVal : String(aVal).localeCompare(String(bVal))) * mult;
|
|
636
|
+
if (diff !== 0) return diff;
|
|
637
|
+
const seqA = seqById.get(a.id);
|
|
638
|
+
const seqB = seqById.get(b.id);
|
|
639
|
+
return seqA != null && seqB != null ? (seqA - seqB) * mult : a.id.localeCompare(b.id);
|
|
640
|
+
});
|
|
641
|
+
const threadIdSet = new Set(threadIds);
|
|
642
|
+
const returnedThreadMessageCount = finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).length;
|
|
643
|
+
const hasMore = perPageInput !== false && returnedThreadMessageCount < total && offset + perPage < total;
|
|
686
644
|
return {
|
|
687
|
-
messages:
|
|
688
|
-
total
|
|
645
|
+
messages: finalMessages,
|
|
646
|
+
total,
|
|
689
647
|
page,
|
|
690
|
-
perPage,
|
|
691
|
-
hasMore
|
|
648
|
+
perPage: perPageForResponse,
|
|
649
|
+
hasMore
|
|
692
650
|
};
|
|
693
651
|
} catch (error) {
|
|
694
652
|
const mastraError = new MastraError(
|
|
695
653
|
{
|
|
696
|
-
id: "
|
|
654
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES", "FAILED"),
|
|
697
655
|
domain: ErrorDomain.STORAGE,
|
|
698
656
|
category: ErrorCategory.THIRD_PARTY,
|
|
699
657
|
details: {
|
|
700
|
-
threadId,
|
|
701
|
-
resourceId: resourceId ?? ""
|
|
702
|
-
page
|
|
658
|
+
threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
|
|
659
|
+
resourceId: resourceId ?? ""
|
|
703
660
|
}
|
|
704
661
|
},
|
|
705
662
|
error
|
|
706
663
|
);
|
|
707
664
|
this.logger?.error?.(mastraError.toString());
|
|
708
|
-
this.logger?.trackException(mastraError);
|
|
709
|
-
return {
|
|
665
|
+
this.logger?.trackException?.(mastraError);
|
|
666
|
+
return {
|
|
667
|
+
messages: [],
|
|
668
|
+
total: 0,
|
|
669
|
+
page,
|
|
670
|
+
perPage: perPageForResponse,
|
|
671
|
+
hasMore: false
|
|
672
|
+
};
|
|
710
673
|
}
|
|
711
674
|
}
|
|
712
|
-
async saveMessages({
|
|
713
|
-
messages
|
|
714
|
-
format
|
|
715
|
-
}) {
|
|
716
|
-
if (messages.length === 0) return messages;
|
|
675
|
+
async saveMessages({ messages }) {
|
|
676
|
+
if (messages.length === 0) return { messages: [] };
|
|
717
677
|
const threadId = messages[0]?.threadId;
|
|
718
678
|
if (!threadId) {
|
|
719
679
|
throw new MastraError({
|
|
720
|
-
id: "
|
|
680
|
+
id: createStorageErrorId("MSSQL", "SAVE_MESSAGES", "INVALID_THREAD_ID"),
|
|
721
681
|
domain: ErrorDomain.STORAGE,
|
|
722
682
|
category: ErrorCategory.THIRD_PARTY,
|
|
723
683
|
text: `Thread ID is required`
|
|
@@ -726,7 +686,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
726
686
|
const thread = await this.getThreadById({ threadId });
|
|
727
687
|
if (!thread) {
|
|
728
688
|
throw new MastraError({
|
|
729
|
-
id: "
|
|
689
|
+
id: createStorageErrorId("MSSQL", "SAVE_MESSAGES", "THREAD_NOT_FOUND"),
|
|
730
690
|
domain: ErrorDomain.STORAGE,
|
|
731
691
|
category: ErrorCategory.THIRD_PARTY,
|
|
732
692
|
text: `Thread ${threadId} not found`,
|
|
@@ -757,7 +717,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
757
717
|
"content",
|
|
758
718
|
typeof message.content === "string" ? message.content : JSON.stringify(message.content)
|
|
759
719
|
);
|
|
760
|
-
request.input("createdAt",
|
|
720
|
+
request.input("createdAt", sql3.DateTime2, message.createdAt);
|
|
761
721
|
request.input("role", message.role);
|
|
762
722
|
request.input("type", message.type || "v2");
|
|
763
723
|
request.input("resourceId", message.resourceId);
|
|
@@ -776,7 +736,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
776
736
|
await request.query(mergeSql);
|
|
777
737
|
}
|
|
778
738
|
const threadReq = transaction.request();
|
|
779
|
-
threadReq.input("updatedAt",
|
|
739
|
+
threadReq.input("updatedAt", sql3.DateTime2, /* @__PURE__ */ new Date());
|
|
780
740
|
threadReq.input("id", threadId);
|
|
781
741
|
await threadReq.query(`UPDATE ${tableThreads} SET [updatedAt] = @updatedAt WHERE id = @id`);
|
|
782
742
|
await transaction.commit();
|
|
@@ -795,12 +755,11 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
795
755
|
return message;
|
|
796
756
|
});
|
|
797
757
|
const list = new MessageList().add(messagesWithParsedContent, "memory");
|
|
798
|
-
|
|
799
|
-
return list.get.all.v1();
|
|
758
|
+
return { messages: list.get.all.db() };
|
|
800
759
|
} catch (error) {
|
|
801
760
|
throw new MastraError(
|
|
802
761
|
{
|
|
803
|
-
id: "
|
|
762
|
+
id: createStorageErrorId("MSSQL", "SAVE_MESSAGES", "FAILED"),
|
|
804
763
|
domain: ErrorDomain.STORAGE,
|
|
805
764
|
category: ErrorCategory.THIRD_PARTY,
|
|
806
765
|
details: { threadId }
|
|
@@ -891,7 +850,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
891
850
|
await transaction.rollback();
|
|
892
851
|
throw new MastraError(
|
|
893
852
|
{
|
|
894
|
-
id: "
|
|
853
|
+
id: createStorageErrorId("MSSQL", "UPDATE_MESSAGES", "FAILED"),
|
|
895
854
|
domain: ErrorDomain.STORAGE,
|
|
896
855
|
category: ErrorCategory.THIRD_PARTY
|
|
897
856
|
},
|
|
@@ -953,7 +912,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
953
912
|
} catch (error) {
|
|
954
913
|
throw new MastraError(
|
|
955
914
|
{
|
|
956
|
-
id: "
|
|
915
|
+
id: createStorageErrorId("MSSQL", "DELETE_MESSAGES", "FAILED"),
|
|
957
916
|
domain: ErrorDomain.STORAGE,
|
|
958
917
|
category: ErrorCategory.THIRD_PARTY,
|
|
959
918
|
details: { messageIds: messageIds.join(", ") }
|
|
@@ -972,14 +931,16 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
972
931
|
return null;
|
|
973
932
|
}
|
|
974
933
|
return {
|
|
975
|
-
|
|
976
|
-
|
|
934
|
+
id: result.id,
|
|
935
|
+
createdAt: result.createdAt,
|
|
936
|
+
updatedAt: result.updatedAt,
|
|
937
|
+
workingMemory: result.workingMemory,
|
|
977
938
|
metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
|
|
978
939
|
};
|
|
979
940
|
} catch (error) {
|
|
980
941
|
const mastraError = new MastraError(
|
|
981
942
|
{
|
|
982
|
-
id: "
|
|
943
|
+
id: createStorageErrorId("MSSQL", "GET_RESOURCE_BY_ID", "FAILED"),
|
|
983
944
|
domain: ErrorDomain.STORAGE,
|
|
984
945
|
category: ErrorCategory.THIRD_PARTY,
|
|
985
946
|
details: { resourceId }
|
|
@@ -987,7 +948,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
987
948
|
error
|
|
988
949
|
);
|
|
989
950
|
this.logger?.error?.(mastraError.toString());
|
|
990
|
-
this.logger?.trackException(mastraError);
|
|
951
|
+
this.logger?.trackException?.(mastraError);
|
|
991
952
|
throw mastraError;
|
|
992
953
|
}
|
|
993
954
|
}
|
|
@@ -996,7 +957,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
996
957
|
tableName: TABLE_RESOURCES,
|
|
997
958
|
record: {
|
|
998
959
|
...resource,
|
|
999
|
-
metadata:
|
|
960
|
+
metadata: resource.metadata
|
|
1000
961
|
}
|
|
1001
962
|
});
|
|
1002
963
|
return resource;
|
|
@@ -1046,7 +1007,7 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
1046
1007
|
} catch (error) {
|
|
1047
1008
|
const mastraError = new MastraError(
|
|
1048
1009
|
{
|
|
1049
|
-
id: "
|
|
1010
|
+
id: createStorageErrorId("MSSQL", "UPDATE_RESOURCE", "FAILED"),
|
|
1050
1011
|
domain: ErrorDomain.STORAGE,
|
|
1051
1012
|
category: ErrorCategory.THIRD_PARTY,
|
|
1052
1013
|
details: { resourceId }
|
|
@@ -1054,62 +1015,381 @@ var MemoryMSSQL = class extends MemoryStorage {
|
|
|
1054
1015
|
error
|
|
1055
1016
|
);
|
|
1056
1017
|
this.logger?.error?.(mastraError.toString());
|
|
1057
|
-
this.logger?.trackException(mastraError);
|
|
1018
|
+
this.logger?.trackException?.(mastraError);
|
|
1058
1019
|
throw mastraError;
|
|
1059
1020
|
}
|
|
1060
1021
|
}
|
|
1061
1022
|
};
|
|
1062
|
-
var
|
|
1023
|
+
var ObservabilityMSSQL = class extends ObservabilityStorage {
|
|
1063
1024
|
pool;
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
case "timestamp":
|
|
1072
|
-
return "DATETIME2(7)";
|
|
1073
|
-
case "uuid":
|
|
1074
|
-
return "UNIQUEIDENTIFIER";
|
|
1075
|
-
case "jsonb":
|
|
1076
|
-
return "NVARCHAR(MAX)";
|
|
1077
|
-
case "integer":
|
|
1078
|
-
return "INT";
|
|
1079
|
-
case "bigint":
|
|
1080
|
-
return "BIGINT";
|
|
1081
|
-
case "float":
|
|
1082
|
-
return "FLOAT";
|
|
1083
|
-
default:
|
|
1084
|
-
throw new MastraError({
|
|
1085
|
-
id: "MASTRA_STORAGE_MSSQL_STORE_TYPE_NOT_SUPPORTED",
|
|
1086
|
-
domain: ErrorDomain.STORAGE,
|
|
1087
|
-
category: ErrorCategory.THIRD_PARTY
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
constructor({ pool, schemaName }) {
|
|
1025
|
+
operations;
|
|
1026
|
+
schema;
|
|
1027
|
+
constructor({
|
|
1028
|
+
pool,
|
|
1029
|
+
operations,
|
|
1030
|
+
schema
|
|
1031
|
+
}) {
|
|
1092
1032
|
super();
|
|
1093
1033
|
this.pool = pool;
|
|
1094
|
-
this.
|
|
1034
|
+
this.operations = operations;
|
|
1035
|
+
this.schema = schema;
|
|
1095
1036
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
request.input("column", column);
|
|
1102
|
-
request.input("columnLower", column.toLowerCase());
|
|
1103
|
-
const result = await request.query(
|
|
1104
|
-
`SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
|
|
1105
|
-
);
|
|
1106
|
-
return result.recordset.length > 0;
|
|
1037
|
+
get tracingStrategy() {
|
|
1038
|
+
return {
|
|
1039
|
+
preferred: "batch-with-updates",
|
|
1040
|
+
supported: ["batch-with-updates", "insert-only"]
|
|
1041
|
+
};
|
|
1107
1042
|
}
|
|
1108
|
-
async
|
|
1109
|
-
|
|
1110
|
-
|
|
1043
|
+
async createSpan(span) {
|
|
1044
|
+
try {
|
|
1045
|
+
const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt;
|
|
1046
|
+
const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt;
|
|
1047
|
+
const record = {
|
|
1048
|
+
...span,
|
|
1049
|
+
startedAt,
|
|
1050
|
+
endedAt
|
|
1051
|
+
// Note: createdAt/updatedAt will be set by default values
|
|
1052
|
+
};
|
|
1053
|
+
return this.operations.insert({ tableName: TABLE_SPANS, record });
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
throw new MastraError(
|
|
1056
|
+
{
|
|
1057
|
+
id: createStorageErrorId("MSSQL", "CREATE_SPAN", "FAILED"),
|
|
1058
|
+
domain: ErrorDomain.STORAGE,
|
|
1059
|
+
category: ErrorCategory.USER,
|
|
1060
|
+
details: {
|
|
1061
|
+
spanId: span.spanId,
|
|
1062
|
+
traceId: span.traceId,
|
|
1063
|
+
spanType: span.spanType,
|
|
1064
|
+
spanName: span.name
|
|
1065
|
+
}
|
|
1066
|
+
},
|
|
1067
|
+
error
|
|
1068
|
+
);
|
|
1111
1069
|
}
|
|
1112
|
-
|
|
1070
|
+
}
|
|
1071
|
+
async getTrace(traceId) {
|
|
1072
|
+
try {
|
|
1073
|
+
const tableName = getTableName({
|
|
1074
|
+
indexName: TABLE_SPANS,
|
|
1075
|
+
schemaName: getSchemaName(this.schema)
|
|
1076
|
+
});
|
|
1077
|
+
const request = this.pool.request();
|
|
1078
|
+
request.input("traceId", traceId);
|
|
1079
|
+
const result = await request.query(
|
|
1080
|
+
`SELECT
|
|
1081
|
+
[traceId], [spanId], [parentSpanId], [name], [scope], [spanType],
|
|
1082
|
+
[attributes], [metadata], [links], [input], [output], [error], [isEvent],
|
|
1083
|
+
[startedAt], [endedAt], [createdAt], [updatedAt]
|
|
1084
|
+
FROM ${tableName}
|
|
1085
|
+
WHERE [traceId] = @traceId
|
|
1086
|
+
ORDER BY [startedAt] DESC`
|
|
1087
|
+
);
|
|
1088
|
+
if (!result.recordset || result.recordset.length === 0) {
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
return {
|
|
1092
|
+
traceId,
|
|
1093
|
+
spans: result.recordset.map(
|
|
1094
|
+
(span) => transformFromSqlRow({
|
|
1095
|
+
tableName: TABLE_SPANS,
|
|
1096
|
+
sqlRow: span
|
|
1097
|
+
})
|
|
1098
|
+
)
|
|
1099
|
+
};
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
throw new MastraError(
|
|
1102
|
+
{
|
|
1103
|
+
id: createStorageErrorId("MSSQL", "GET_TRACE", "FAILED"),
|
|
1104
|
+
domain: ErrorDomain.STORAGE,
|
|
1105
|
+
category: ErrorCategory.USER,
|
|
1106
|
+
details: {
|
|
1107
|
+
traceId
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
error
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
async updateSpan({
|
|
1115
|
+
spanId,
|
|
1116
|
+
traceId,
|
|
1117
|
+
updates
|
|
1118
|
+
}) {
|
|
1119
|
+
try {
|
|
1120
|
+
const data = { ...updates };
|
|
1121
|
+
if (data.endedAt instanceof Date) {
|
|
1122
|
+
data.endedAt = data.endedAt.toISOString();
|
|
1123
|
+
}
|
|
1124
|
+
if (data.startedAt instanceof Date) {
|
|
1125
|
+
data.startedAt = data.startedAt.toISOString();
|
|
1126
|
+
}
|
|
1127
|
+
await this.operations.update({
|
|
1128
|
+
tableName: TABLE_SPANS,
|
|
1129
|
+
keys: { spanId, traceId },
|
|
1130
|
+
data
|
|
1131
|
+
});
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
throw new MastraError(
|
|
1134
|
+
{
|
|
1135
|
+
id: createStorageErrorId("MSSQL", "UPDATE_SPAN", "FAILED"),
|
|
1136
|
+
domain: ErrorDomain.STORAGE,
|
|
1137
|
+
category: ErrorCategory.USER,
|
|
1138
|
+
details: {
|
|
1139
|
+
spanId,
|
|
1140
|
+
traceId
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
error
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
async getTracesPaginated({
|
|
1148
|
+
filters,
|
|
1149
|
+
pagination
|
|
1150
|
+
}) {
|
|
1151
|
+
const page = pagination?.page ?? 0;
|
|
1152
|
+
const perPage = pagination?.perPage ?? 10;
|
|
1153
|
+
const { entityId, entityType, ...actualFilters } = filters || {};
|
|
1154
|
+
const filtersWithDateRange = {
|
|
1155
|
+
...actualFilters,
|
|
1156
|
+
...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
|
|
1157
|
+
parentSpanId: null
|
|
1158
|
+
// Only get root spans for traces
|
|
1159
|
+
};
|
|
1160
|
+
const whereClause = prepareWhereClause(filtersWithDateRange);
|
|
1161
|
+
let actualWhereClause = whereClause.sql;
|
|
1162
|
+
const params = { ...whereClause.params };
|
|
1163
|
+
let currentParamIndex = Object.keys(params).length + 1;
|
|
1164
|
+
if (entityId && entityType) {
|
|
1165
|
+
let name = "";
|
|
1166
|
+
if (entityType === "workflow") {
|
|
1167
|
+
name = `workflow run: '${entityId}'`;
|
|
1168
|
+
} else if (entityType === "agent") {
|
|
1169
|
+
name = `agent run: '${entityId}'`;
|
|
1170
|
+
} else {
|
|
1171
|
+
const error = new MastraError({
|
|
1172
|
+
id: createStorageErrorId("MSSQL", "GET_TRACES_PAGINATED", "INVALID_ENTITY_TYPE"),
|
|
1173
|
+
domain: ErrorDomain.STORAGE,
|
|
1174
|
+
category: ErrorCategory.USER,
|
|
1175
|
+
details: {
|
|
1176
|
+
entityType
|
|
1177
|
+
},
|
|
1178
|
+
text: `Cannot filter by entity type: ${entityType}`
|
|
1179
|
+
});
|
|
1180
|
+
throw error;
|
|
1181
|
+
}
|
|
1182
|
+
const entityParam = `p${currentParamIndex++}`;
|
|
1183
|
+
if (actualWhereClause) {
|
|
1184
|
+
actualWhereClause += ` AND [name] = @${entityParam}`;
|
|
1185
|
+
} else {
|
|
1186
|
+
actualWhereClause = ` WHERE [name] = @${entityParam}`;
|
|
1187
|
+
}
|
|
1188
|
+
params[entityParam] = name;
|
|
1189
|
+
}
|
|
1190
|
+
const tableName = getTableName({
|
|
1191
|
+
indexName: TABLE_SPANS,
|
|
1192
|
+
schemaName: getSchemaName(this.schema)
|
|
1193
|
+
});
|
|
1194
|
+
try {
|
|
1195
|
+
const countRequest = this.pool.request();
|
|
1196
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1197
|
+
countRequest.input(key, value);
|
|
1198
|
+
});
|
|
1199
|
+
const countResult = await countRequest.query(
|
|
1200
|
+
`SELECT COUNT(*) as count FROM ${tableName}${actualWhereClause}`
|
|
1201
|
+
);
|
|
1202
|
+
const total = countResult.recordset[0]?.count ?? 0;
|
|
1203
|
+
if (total === 0) {
|
|
1204
|
+
return {
|
|
1205
|
+
pagination: {
|
|
1206
|
+
total: 0,
|
|
1207
|
+
page,
|
|
1208
|
+
perPage,
|
|
1209
|
+
hasMore: false
|
|
1210
|
+
},
|
|
1211
|
+
spans: []
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
const dataRequest = this.pool.request();
|
|
1215
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1216
|
+
dataRequest.input(key, value);
|
|
1217
|
+
});
|
|
1218
|
+
dataRequest.input("offset", page * perPage);
|
|
1219
|
+
dataRequest.input("limit", perPage);
|
|
1220
|
+
const dataResult = await dataRequest.query(
|
|
1221
|
+
`SELECT * FROM ${tableName}${actualWhereClause} ORDER BY [startedAt] DESC OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
|
|
1222
|
+
);
|
|
1223
|
+
const spans = dataResult.recordset.map(
|
|
1224
|
+
(row) => transformFromSqlRow({
|
|
1225
|
+
tableName: TABLE_SPANS,
|
|
1226
|
+
sqlRow: row
|
|
1227
|
+
})
|
|
1228
|
+
);
|
|
1229
|
+
return {
|
|
1230
|
+
pagination: {
|
|
1231
|
+
total,
|
|
1232
|
+
page,
|
|
1233
|
+
perPage,
|
|
1234
|
+
hasMore: (page + 1) * perPage < total
|
|
1235
|
+
},
|
|
1236
|
+
spans
|
|
1237
|
+
};
|
|
1238
|
+
} catch (error) {
|
|
1239
|
+
throw new MastraError(
|
|
1240
|
+
{
|
|
1241
|
+
id: createStorageErrorId("MSSQL", "GET_TRACES_PAGINATED", "FAILED"),
|
|
1242
|
+
domain: ErrorDomain.STORAGE,
|
|
1243
|
+
category: ErrorCategory.USER
|
|
1244
|
+
},
|
|
1245
|
+
error
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
async batchCreateSpans(args) {
|
|
1250
|
+
if (!args.records || args.records.length === 0) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
try {
|
|
1254
|
+
await this.operations.batchInsert({
|
|
1255
|
+
tableName: TABLE_SPANS,
|
|
1256
|
+
records: args.records.map((span) => ({
|
|
1257
|
+
...span,
|
|
1258
|
+
startedAt: span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt,
|
|
1259
|
+
endedAt: span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt
|
|
1260
|
+
}))
|
|
1261
|
+
});
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
throw new MastraError(
|
|
1264
|
+
{
|
|
1265
|
+
id: createStorageErrorId("MSSQL", "BATCH_CREATE_SPANS", "FAILED"),
|
|
1266
|
+
domain: ErrorDomain.STORAGE,
|
|
1267
|
+
category: ErrorCategory.USER,
|
|
1268
|
+
details: {
|
|
1269
|
+
count: args.records.length
|
|
1270
|
+
}
|
|
1271
|
+
},
|
|
1272
|
+
error
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
async batchUpdateSpans(args) {
|
|
1277
|
+
if (!args.records || args.records.length === 0) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
try {
|
|
1281
|
+
const updates = args.records.map(({ traceId, spanId, updates: data }) => {
|
|
1282
|
+
const processedData = { ...data };
|
|
1283
|
+
if (processedData.endedAt instanceof Date) {
|
|
1284
|
+
processedData.endedAt = processedData.endedAt.toISOString();
|
|
1285
|
+
}
|
|
1286
|
+
if (processedData.startedAt instanceof Date) {
|
|
1287
|
+
processedData.startedAt = processedData.startedAt.toISOString();
|
|
1288
|
+
}
|
|
1289
|
+
return {
|
|
1290
|
+
keys: { spanId, traceId },
|
|
1291
|
+
data: processedData
|
|
1292
|
+
};
|
|
1293
|
+
});
|
|
1294
|
+
await this.operations.batchUpdate({
|
|
1295
|
+
tableName: TABLE_SPANS,
|
|
1296
|
+
updates
|
|
1297
|
+
});
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
throw new MastraError(
|
|
1300
|
+
{
|
|
1301
|
+
id: createStorageErrorId("MSSQL", "BATCH_UPDATE_SPANS", "FAILED"),
|
|
1302
|
+
domain: ErrorDomain.STORAGE,
|
|
1303
|
+
category: ErrorCategory.USER,
|
|
1304
|
+
details: {
|
|
1305
|
+
count: args.records.length
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
error
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
async batchDeleteTraces(args) {
|
|
1313
|
+
if (!args.traceIds || args.traceIds.length === 0) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
try {
|
|
1317
|
+
const keys = args.traceIds.map((traceId) => ({ traceId }));
|
|
1318
|
+
await this.operations.batchDelete({
|
|
1319
|
+
tableName: TABLE_SPANS,
|
|
1320
|
+
keys
|
|
1321
|
+
});
|
|
1322
|
+
} catch (error) {
|
|
1323
|
+
throw new MastraError(
|
|
1324
|
+
{
|
|
1325
|
+
id: createStorageErrorId("MSSQL", "BATCH_DELETE_TRACES", "FAILED"),
|
|
1326
|
+
domain: ErrorDomain.STORAGE,
|
|
1327
|
+
category: ErrorCategory.USER,
|
|
1328
|
+
details: {
|
|
1329
|
+
count: args.traceIds.length
|
|
1330
|
+
}
|
|
1331
|
+
},
|
|
1332
|
+
error
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
var StoreOperationsMSSQL = class extends StoreOperations {
|
|
1338
|
+
pool;
|
|
1339
|
+
schemaName;
|
|
1340
|
+
setupSchemaPromise = null;
|
|
1341
|
+
schemaSetupComplete = void 0;
|
|
1342
|
+
getSqlType(type, isPrimaryKey = false, useLargeStorage = false) {
|
|
1343
|
+
switch (type) {
|
|
1344
|
+
case "text":
|
|
1345
|
+
if (useLargeStorage) {
|
|
1346
|
+
return "NVARCHAR(MAX)";
|
|
1347
|
+
}
|
|
1348
|
+
return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(400)";
|
|
1349
|
+
case "timestamp":
|
|
1350
|
+
return "DATETIME2(7)";
|
|
1351
|
+
case "uuid":
|
|
1352
|
+
return "UNIQUEIDENTIFIER";
|
|
1353
|
+
case "jsonb":
|
|
1354
|
+
return "NVARCHAR(MAX)";
|
|
1355
|
+
case "integer":
|
|
1356
|
+
return "INT";
|
|
1357
|
+
case "bigint":
|
|
1358
|
+
return "BIGINT";
|
|
1359
|
+
case "float":
|
|
1360
|
+
return "FLOAT";
|
|
1361
|
+
case "boolean":
|
|
1362
|
+
return "BIT";
|
|
1363
|
+
default:
|
|
1364
|
+
throw new MastraError({
|
|
1365
|
+
id: createStorageErrorId("MSSQL", "TYPE", "NOT_SUPPORTED"),
|
|
1366
|
+
domain: ErrorDomain.STORAGE,
|
|
1367
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
constructor({ pool, schemaName }) {
|
|
1372
|
+
super();
|
|
1373
|
+
this.pool = pool;
|
|
1374
|
+
this.schemaName = schemaName;
|
|
1375
|
+
}
|
|
1376
|
+
async hasColumn(table, column) {
|
|
1377
|
+
const schema = this.schemaName || "dbo";
|
|
1378
|
+
const request = this.pool.request();
|
|
1379
|
+
request.input("schema", schema);
|
|
1380
|
+
request.input("table", table);
|
|
1381
|
+
request.input("column", column);
|
|
1382
|
+
request.input("columnLower", column.toLowerCase());
|
|
1383
|
+
const result = await request.query(
|
|
1384
|
+
`SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
|
|
1385
|
+
);
|
|
1386
|
+
return result.recordset.length > 0;
|
|
1387
|
+
}
|
|
1388
|
+
async setupSchema() {
|
|
1389
|
+
if (!this.schemaName || this.schemaSetupComplete) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (!this.setupSchemaPromise) {
|
|
1113
1393
|
this.setupSchemaPromise = (async () => {
|
|
1114
1394
|
try {
|
|
1115
1395
|
const checkRequest = this.pool.request();
|
|
@@ -1142,27 +1422,33 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1142
1422
|
}
|
|
1143
1423
|
await this.setupSchemaPromise;
|
|
1144
1424
|
}
|
|
1145
|
-
async insert({
|
|
1425
|
+
async insert({
|
|
1426
|
+
tableName,
|
|
1427
|
+
record,
|
|
1428
|
+
transaction
|
|
1429
|
+
}) {
|
|
1146
1430
|
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}`,
|
|
1431
|
+
const columns = Object.keys(record);
|
|
1432
|
+
const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
|
|
1433
|
+
const paramNames = columns.map((_, i) => `@param${i}`);
|
|
1434
|
+
const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${parsedColumns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
|
|
1435
|
+
const request = transaction ? transaction.request() : this.pool.request();
|
|
1436
|
+
columns.forEach((col, i) => {
|
|
1437
|
+
const value = record[col];
|
|
1438
|
+
const preparedValue = this.prepareValue(value, col, tableName);
|
|
1439
|
+
if (preparedValue instanceof Date) {
|
|
1440
|
+
request.input(`param${i}`, sql3.DateTime2, preparedValue);
|
|
1441
|
+
} else if (preparedValue === null || preparedValue === void 0) {
|
|
1442
|
+
request.input(`param${i}`, this.getMssqlType(tableName, col), null);
|
|
1157
1443
|
} else {
|
|
1158
|
-
request.input(`param${i}`,
|
|
1444
|
+
request.input(`param${i}`, preparedValue);
|
|
1159
1445
|
}
|
|
1160
1446
|
});
|
|
1161
1447
|
await request.query(insertSql);
|
|
1162
1448
|
} catch (error) {
|
|
1163
1449
|
throw new MastraError(
|
|
1164
1450
|
{
|
|
1165
|
-
id: "
|
|
1451
|
+
id: createStorageErrorId("MSSQL", "INSERT", "FAILED"),
|
|
1166
1452
|
domain: ErrorDomain.STORAGE,
|
|
1167
1453
|
category: ErrorCategory.THIRD_PARTY,
|
|
1168
1454
|
details: {
|
|
@@ -1179,7 +1465,7 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1179
1465
|
try {
|
|
1180
1466
|
await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
|
|
1181
1467
|
} catch (truncateError) {
|
|
1182
|
-
if (truncateError
|
|
1468
|
+
if (truncateError?.number === 4712) {
|
|
1183
1469
|
await this.pool.request().query(`DELETE FROM ${fullTableName}`);
|
|
1184
1470
|
} else {
|
|
1185
1471
|
throw truncateError;
|
|
@@ -1188,7 +1474,7 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1188
1474
|
} catch (error) {
|
|
1189
1475
|
throw new MastraError(
|
|
1190
1476
|
{
|
|
1191
|
-
id: "
|
|
1477
|
+
id: createStorageErrorId("MSSQL", "CLEAR_TABLE", "FAILED"),
|
|
1192
1478
|
domain: ErrorDomain.STORAGE,
|
|
1193
1479
|
category: ErrorCategory.THIRD_PARTY,
|
|
1194
1480
|
details: {
|
|
@@ -1202,9 +1488,11 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1202
1488
|
getDefaultValue(type) {
|
|
1203
1489
|
switch (type) {
|
|
1204
1490
|
case "timestamp":
|
|
1205
|
-
return "DEFAULT
|
|
1491
|
+
return "DEFAULT SYSUTCDATETIME()";
|
|
1206
1492
|
case "jsonb":
|
|
1207
1493
|
return "DEFAULT N'{}'";
|
|
1494
|
+
case "boolean":
|
|
1495
|
+
return "DEFAULT 0";
|
|
1208
1496
|
default:
|
|
1209
1497
|
return super.getDefaultValue(type);
|
|
1210
1498
|
}
|
|
@@ -1215,13 +1503,29 @@ var StoreOperationsMSSQL = class extends StoreOperations {
|
|
|
1215
1503
|
}) {
|
|
1216
1504
|
try {
|
|
1217
1505
|
const uniqueConstraintColumns = tableName === TABLE_WORKFLOW_SNAPSHOT ? ["workflow_name", "run_id"] : [];
|
|
1506
|
+
const largeDataColumns = [
|
|
1507
|
+
"workingMemory",
|
|
1508
|
+
"snapshot",
|
|
1509
|
+
"metadata",
|
|
1510
|
+
"content",
|
|
1511
|
+
// messages.content - can be very long conversation content
|
|
1512
|
+
"input",
|
|
1513
|
+
// evals.input - test input data
|
|
1514
|
+
"output",
|
|
1515
|
+
// evals.output - test output data
|
|
1516
|
+
"instructions",
|
|
1517
|
+
// evals.instructions - evaluation instructions
|
|
1518
|
+
"other"
|
|
1519
|
+
// traces.other - additional trace data
|
|
1520
|
+
];
|
|
1218
1521
|
const columns = Object.entries(schema).map(([name, def]) => {
|
|
1219
1522
|
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1220
1523
|
const constraints = [];
|
|
1221
1524
|
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
1222
1525
|
if (!def.nullable) constraints.push("NOT NULL");
|
|
1223
1526
|
const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
|
|
1224
|
-
|
|
1527
|
+
const useLargeStorage = largeDataColumns.includes(name);
|
|
1528
|
+
return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
|
|
1225
1529
|
}).join(",\n");
|
|
1226
1530
|
if (this.schemaName) {
|
|
1227
1531
|
await this.setupSchema();
|
|
@@ -1273,7 +1577,7 @@ ${columns}
|
|
|
1273
1577
|
} catch (error) {
|
|
1274
1578
|
throw new MastraError(
|
|
1275
1579
|
{
|
|
1276
|
-
id: "
|
|
1580
|
+
id: createStorageErrorId("MSSQL", "CREATE_TABLE", "FAILED"),
|
|
1277
1581
|
domain: ErrorDomain.STORAGE,
|
|
1278
1582
|
category: ErrorCategory.THIRD_PARTY,
|
|
1279
1583
|
details: {
|
|
@@ -1308,7 +1612,19 @@ ${columns}
|
|
|
1308
1612
|
const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
|
|
1309
1613
|
if (!columnExists) {
|
|
1310
1614
|
const columnDef = schema[columnName];
|
|
1311
|
-
const
|
|
1615
|
+
const largeDataColumns = [
|
|
1616
|
+
"workingMemory",
|
|
1617
|
+
"snapshot",
|
|
1618
|
+
"metadata",
|
|
1619
|
+
"content",
|
|
1620
|
+
"input",
|
|
1621
|
+
"output",
|
|
1622
|
+
"instructions",
|
|
1623
|
+
"other"
|
|
1624
|
+
];
|
|
1625
|
+
const useLargeStorage = largeDataColumns.includes(columnName);
|
|
1626
|
+
const isIndexed = !!columnDef.primaryKey;
|
|
1627
|
+
const sqlType = this.getSqlType(columnDef.type, isIndexed, useLargeStorage);
|
|
1312
1628
|
const nullable = columnDef.nullable === false ? "NOT NULL" : "";
|
|
1313
1629
|
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
|
|
1314
1630
|
const parsedColumnName = parseSqlIdentifier(columnName, "column name");
|
|
@@ -1321,7 +1637,7 @@ ${columns}
|
|
|
1321
1637
|
} catch (error) {
|
|
1322
1638
|
throw new MastraError(
|
|
1323
1639
|
{
|
|
1324
|
-
id: "
|
|
1640
|
+
id: createStorageErrorId("MSSQL", "ALTER_TABLE", "FAILED"),
|
|
1325
1641
|
domain: ErrorDomain.STORAGE,
|
|
1326
1642
|
category: ErrorCategory.THIRD_PARTY,
|
|
1327
1643
|
details: {
|
|
@@ -1336,13 +1652,17 @@ ${columns}
|
|
|
1336
1652
|
try {
|
|
1337
1653
|
const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
|
|
1338
1654
|
const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
|
|
1339
|
-
const
|
|
1340
|
-
const sql7 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
|
|
1655
|
+
const sql5 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
|
|
1341
1656
|
const request = this.pool.request();
|
|
1342
|
-
|
|
1343
|
-
|
|
1657
|
+
keyEntries.forEach(([key, value], i) => {
|
|
1658
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1659
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1660
|
+
request.input(`param${i}`, this.getMssqlType(tableName, key), null);
|
|
1661
|
+
} else {
|
|
1662
|
+
request.input(`param${i}`, preparedValue);
|
|
1663
|
+
}
|
|
1344
1664
|
});
|
|
1345
|
-
const resultSet = await request.query(
|
|
1665
|
+
const resultSet = await request.query(sql5);
|
|
1346
1666
|
const result = resultSet.recordset[0] || null;
|
|
1347
1667
|
if (!result) {
|
|
1348
1668
|
return null;
|
|
@@ -1358,7 +1678,7 @@ ${columns}
|
|
|
1358
1678
|
} catch (error) {
|
|
1359
1679
|
throw new MastraError(
|
|
1360
1680
|
{
|
|
1361
|
-
id: "
|
|
1681
|
+
id: createStorageErrorId("MSSQL", "LOAD", "FAILED"),
|
|
1362
1682
|
domain: ErrorDomain.STORAGE,
|
|
1363
1683
|
category: ErrorCategory.THIRD_PARTY,
|
|
1364
1684
|
details: {
|
|
@@ -1374,14 +1694,14 @@ ${columns}
|
|
|
1374
1694
|
try {
|
|
1375
1695
|
await transaction.begin();
|
|
1376
1696
|
for (const record of records) {
|
|
1377
|
-
await this.insert({ tableName, record });
|
|
1697
|
+
await this.insert({ tableName, record, transaction });
|
|
1378
1698
|
}
|
|
1379
1699
|
await transaction.commit();
|
|
1380
1700
|
} catch (error) {
|
|
1381
1701
|
await transaction.rollback();
|
|
1382
1702
|
throw new MastraError(
|
|
1383
1703
|
{
|
|
1384
|
-
id: "
|
|
1704
|
+
id: createStorageErrorId("MSSQL", "BATCH_INSERT", "FAILED"),
|
|
1385
1705
|
domain: ErrorDomain.STORAGE,
|
|
1386
1706
|
category: ErrorCategory.THIRD_PARTY,
|
|
1387
1707
|
details: {
|
|
@@ -1400,7 +1720,7 @@ ${columns}
|
|
|
1400
1720
|
} catch (error) {
|
|
1401
1721
|
throw new MastraError(
|
|
1402
1722
|
{
|
|
1403
|
-
id: "
|
|
1723
|
+
id: createStorageErrorId("MSSQL", "DROP_TABLE", "FAILED"),
|
|
1404
1724
|
domain: ErrorDomain.STORAGE,
|
|
1405
1725
|
category: ErrorCategory.THIRD_PARTY,
|
|
1406
1726
|
details: {
|
|
@@ -1411,29 +1731,568 @@ ${columns}
|
|
|
1411
1731
|
);
|
|
1412
1732
|
}
|
|
1413
1733
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1734
|
+
/**
|
|
1735
|
+
* Prepares a value for database operations, handling Date objects and JSON serialization
|
|
1736
|
+
*/
|
|
1737
|
+
prepareValue(value, columnName, tableName) {
|
|
1738
|
+
if (value === null || value === void 0) {
|
|
1739
|
+
return value;
|
|
1740
|
+
}
|
|
1741
|
+
if (value instanceof Date) {
|
|
1742
|
+
return value;
|
|
1743
|
+
}
|
|
1744
|
+
const schema = TABLE_SCHEMAS[tableName];
|
|
1745
|
+
const columnSchema = schema?.[columnName];
|
|
1746
|
+
if (columnSchema?.type === "boolean") {
|
|
1747
|
+
return value ? 1 : 0;
|
|
1748
|
+
}
|
|
1749
|
+
if (columnSchema?.type === "jsonb") {
|
|
1750
|
+
if (typeof value === "string") {
|
|
1751
|
+
const trimmed = value.trim();
|
|
1752
|
+
if (trimmed.length > 0) {
|
|
1753
|
+
try {
|
|
1754
|
+
JSON.parse(trimmed);
|
|
1755
|
+
return trimmed;
|
|
1756
|
+
} catch {
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return JSON.stringify(value);
|
|
1760
|
+
}
|
|
1761
|
+
if (typeof value === "bigint") {
|
|
1762
|
+
return value.toString();
|
|
1763
|
+
}
|
|
1764
|
+
return JSON.stringify(value);
|
|
1765
|
+
}
|
|
1766
|
+
if (typeof value === "object") {
|
|
1767
|
+
return JSON.stringify(value);
|
|
1768
|
+
}
|
|
1769
|
+
return value;
|
|
1420
1770
|
}
|
|
1421
|
-
|
|
1771
|
+
/**
|
|
1772
|
+
* Maps TABLE_SCHEMAS types to mssql param types (used when value is null)
|
|
1773
|
+
*/
|
|
1774
|
+
getMssqlType(tableName, columnName) {
|
|
1775
|
+
const col = TABLE_SCHEMAS[tableName]?.[columnName];
|
|
1776
|
+
switch (col?.type) {
|
|
1777
|
+
case "text":
|
|
1778
|
+
return sql3.NVarChar;
|
|
1779
|
+
case "timestamp":
|
|
1780
|
+
return sql3.DateTime2;
|
|
1781
|
+
case "uuid":
|
|
1782
|
+
return sql3.UniqueIdentifier;
|
|
1783
|
+
case "jsonb":
|
|
1784
|
+
return sql3.NVarChar;
|
|
1785
|
+
case "integer":
|
|
1786
|
+
return sql3.Int;
|
|
1787
|
+
case "bigint":
|
|
1788
|
+
return sql3.BigInt;
|
|
1789
|
+
case "float":
|
|
1790
|
+
return sql3.Float;
|
|
1791
|
+
case "boolean":
|
|
1792
|
+
return sql3.Bit;
|
|
1793
|
+
default:
|
|
1794
|
+
return sql3.NVarChar;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Update a single record in the database
|
|
1799
|
+
*/
|
|
1800
|
+
async update({
|
|
1801
|
+
tableName,
|
|
1802
|
+
keys,
|
|
1803
|
+
data,
|
|
1804
|
+
transaction
|
|
1805
|
+
}) {
|
|
1806
|
+
try {
|
|
1807
|
+
if (!data || Object.keys(data).length === 0) {
|
|
1808
|
+
throw new MastraError({
|
|
1809
|
+
id: createStorageErrorId("MSSQL", "UPDATE", "EMPTY_DATA"),
|
|
1810
|
+
domain: ErrorDomain.STORAGE,
|
|
1811
|
+
category: ErrorCategory.USER,
|
|
1812
|
+
text: "Cannot update with empty data payload"
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
if (!keys || Object.keys(keys).length === 0) {
|
|
1816
|
+
throw new MastraError({
|
|
1817
|
+
id: createStorageErrorId("MSSQL", "UPDATE", "EMPTY_KEYS"),
|
|
1818
|
+
domain: ErrorDomain.STORAGE,
|
|
1819
|
+
category: ErrorCategory.USER,
|
|
1820
|
+
text: "Cannot update without keys to identify records"
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
const setClauses = [];
|
|
1824
|
+
const request = transaction ? transaction.request() : this.pool.request();
|
|
1825
|
+
let paramIndex = 0;
|
|
1826
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
1827
|
+
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
1828
|
+
const paramName = `set${paramIndex++}`;
|
|
1829
|
+
setClauses.push(`[${parsedKey}] = @${paramName}`);
|
|
1830
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1831
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1832
|
+
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1833
|
+
} else {
|
|
1834
|
+
request.input(paramName, preparedValue);
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
const whereConditions = [];
|
|
1838
|
+
Object.entries(keys).forEach(([key, value]) => {
|
|
1839
|
+
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
1840
|
+
const paramName = `where${paramIndex++}`;
|
|
1841
|
+
whereConditions.push(`[${parsedKey}] = @${paramName}`);
|
|
1842
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1843
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1844
|
+
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1845
|
+
} else {
|
|
1846
|
+
request.input(paramName, preparedValue);
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1849
|
+
const tableName_ = getTableName({
|
|
1850
|
+
indexName: tableName,
|
|
1851
|
+
schemaName: getSchemaName(this.schemaName)
|
|
1852
|
+
});
|
|
1853
|
+
const updateSql = `UPDATE ${tableName_} SET ${setClauses.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
|
|
1854
|
+
await request.query(updateSql);
|
|
1855
|
+
} catch (error) {
|
|
1856
|
+
throw new MastraError(
|
|
1857
|
+
{
|
|
1858
|
+
id: createStorageErrorId("MSSQL", "UPDATE", "FAILED"),
|
|
1859
|
+
domain: ErrorDomain.STORAGE,
|
|
1860
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1861
|
+
details: {
|
|
1862
|
+
tableName
|
|
1863
|
+
}
|
|
1864
|
+
},
|
|
1865
|
+
error
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Update multiple records in a single batch transaction
|
|
1871
|
+
*/
|
|
1872
|
+
async batchUpdate({
|
|
1873
|
+
tableName,
|
|
1874
|
+
updates
|
|
1875
|
+
}) {
|
|
1876
|
+
const transaction = this.pool.transaction();
|
|
1877
|
+
try {
|
|
1878
|
+
await transaction.begin();
|
|
1879
|
+
for (const { keys, data } of updates) {
|
|
1880
|
+
await this.update({ tableName, keys, data, transaction });
|
|
1881
|
+
}
|
|
1882
|
+
await transaction.commit();
|
|
1883
|
+
} catch (error) {
|
|
1884
|
+
await transaction.rollback();
|
|
1885
|
+
throw new MastraError(
|
|
1886
|
+
{
|
|
1887
|
+
id: createStorageErrorId("MSSQL", "BATCH_UPDATE", "FAILED"),
|
|
1888
|
+
domain: ErrorDomain.STORAGE,
|
|
1889
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1890
|
+
details: {
|
|
1891
|
+
tableName,
|
|
1892
|
+
numberOfRecords: updates.length
|
|
1893
|
+
}
|
|
1894
|
+
},
|
|
1895
|
+
error
|
|
1896
|
+
);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Delete multiple records by keys
|
|
1901
|
+
*/
|
|
1902
|
+
async batchDelete({ tableName, keys }) {
|
|
1903
|
+
if (keys.length === 0) {
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
const tableName_ = getTableName({
|
|
1907
|
+
indexName: tableName,
|
|
1908
|
+
schemaName: getSchemaName(this.schemaName)
|
|
1909
|
+
});
|
|
1910
|
+
const transaction = this.pool.transaction();
|
|
1911
|
+
try {
|
|
1912
|
+
await transaction.begin();
|
|
1913
|
+
for (const keySet of keys) {
|
|
1914
|
+
const conditions = [];
|
|
1915
|
+
const request = transaction.request();
|
|
1916
|
+
let paramIndex = 0;
|
|
1917
|
+
Object.entries(keySet).forEach(([key, value]) => {
|
|
1918
|
+
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
1919
|
+
const paramName = `p${paramIndex++}`;
|
|
1920
|
+
conditions.push(`[${parsedKey}] = @${paramName}`);
|
|
1921
|
+
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1922
|
+
if (preparedValue === null || preparedValue === void 0) {
|
|
1923
|
+
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1924
|
+
} else {
|
|
1925
|
+
request.input(paramName, preparedValue);
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
const deleteSql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
|
|
1929
|
+
await request.query(deleteSql);
|
|
1930
|
+
}
|
|
1931
|
+
await transaction.commit();
|
|
1932
|
+
} catch (error) {
|
|
1933
|
+
await transaction.rollback();
|
|
1934
|
+
throw new MastraError(
|
|
1935
|
+
{
|
|
1936
|
+
id: createStorageErrorId("MSSQL", "BATCH_DELETE", "FAILED"),
|
|
1937
|
+
domain: ErrorDomain.STORAGE,
|
|
1938
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1939
|
+
details: {
|
|
1940
|
+
tableName,
|
|
1941
|
+
numberOfRecords: keys.length
|
|
1942
|
+
}
|
|
1943
|
+
},
|
|
1944
|
+
error
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Create a new index on a table
|
|
1950
|
+
*/
|
|
1951
|
+
async createIndex(options) {
|
|
1952
|
+
try {
|
|
1953
|
+
const { name, table, columns, unique = false, where } = options;
|
|
1954
|
+
const schemaName = this.schemaName || "dbo";
|
|
1955
|
+
const fullTableName = getTableName({
|
|
1956
|
+
indexName: table,
|
|
1957
|
+
schemaName: getSchemaName(this.schemaName)
|
|
1958
|
+
});
|
|
1959
|
+
const indexNameSafe = parseSqlIdentifier(name, "index name");
|
|
1960
|
+
const checkRequest = this.pool.request();
|
|
1961
|
+
checkRequest.input("indexName", indexNameSafe);
|
|
1962
|
+
checkRequest.input("schemaName", schemaName);
|
|
1963
|
+
checkRequest.input("tableName", table);
|
|
1964
|
+
const indexExists = await checkRequest.query(`
|
|
1965
|
+
SELECT 1 as found
|
|
1966
|
+
FROM sys.indexes i
|
|
1967
|
+
INNER JOIN sys.tables t ON i.object_id = t.object_id
|
|
1968
|
+
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
1969
|
+
WHERE i.name = @indexName
|
|
1970
|
+
AND s.name = @schemaName
|
|
1971
|
+
AND t.name = @tableName
|
|
1972
|
+
`);
|
|
1973
|
+
if (indexExists.recordset && indexExists.recordset.length > 0) {
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
const uniqueStr = unique ? "UNIQUE " : "";
|
|
1977
|
+
const columnsStr = columns.map((col) => {
|
|
1978
|
+
if (col.includes(" DESC") || col.includes(" ASC")) {
|
|
1979
|
+
const [colName, ...modifiers] = col.split(" ");
|
|
1980
|
+
if (!colName) {
|
|
1981
|
+
throw new Error(`Invalid column specification: ${col}`);
|
|
1982
|
+
}
|
|
1983
|
+
return `[${parseSqlIdentifier(colName, "column name")}] ${modifiers.join(" ")}`;
|
|
1984
|
+
}
|
|
1985
|
+
return `[${parseSqlIdentifier(col, "column name")}]`;
|
|
1986
|
+
}).join(", ");
|
|
1987
|
+
const whereStr = where ? ` WHERE ${where}` : "";
|
|
1988
|
+
const createIndexSql = `CREATE ${uniqueStr}INDEX [${indexNameSafe}] ON ${fullTableName} (${columnsStr})${whereStr}`;
|
|
1989
|
+
await this.pool.request().query(createIndexSql);
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
throw new MastraError(
|
|
1992
|
+
{
|
|
1993
|
+
id: createStorageErrorId("MSSQL", "INDEX_CREATE", "FAILED"),
|
|
1994
|
+
domain: ErrorDomain.STORAGE,
|
|
1995
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1996
|
+
details: {
|
|
1997
|
+
indexName: options.name,
|
|
1998
|
+
tableName: options.table
|
|
1999
|
+
}
|
|
2000
|
+
},
|
|
2001
|
+
error
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Drop an existing index
|
|
2007
|
+
*/
|
|
2008
|
+
async dropIndex(indexName) {
|
|
2009
|
+
try {
|
|
2010
|
+
const schemaName = this.schemaName || "dbo";
|
|
2011
|
+
const indexNameSafe = parseSqlIdentifier(indexName, "index name");
|
|
2012
|
+
const checkRequest = this.pool.request();
|
|
2013
|
+
checkRequest.input("indexName", indexNameSafe);
|
|
2014
|
+
checkRequest.input("schemaName", schemaName);
|
|
2015
|
+
const result = await checkRequest.query(`
|
|
2016
|
+
SELECT t.name as table_name
|
|
2017
|
+
FROM sys.indexes i
|
|
2018
|
+
INNER JOIN sys.tables t ON i.object_id = t.object_id
|
|
2019
|
+
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
2020
|
+
WHERE i.name = @indexName
|
|
2021
|
+
AND s.name = @schemaName
|
|
2022
|
+
`);
|
|
2023
|
+
if (!result.recordset || result.recordset.length === 0) {
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
if (result.recordset.length > 1) {
|
|
2027
|
+
const tables = result.recordset.map((r) => r.table_name).join(", ");
|
|
2028
|
+
throw new MastraError({
|
|
2029
|
+
id: createStorageErrorId("MSSQL", "INDEX", "AMBIGUOUS"),
|
|
2030
|
+
domain: ErrorDomain.STORAGE,
|
|
2031
|
+
category: ErrorCategory.USER,
|
|
2032
|
+
text: `Index "${indexNameSafe}" exists on multiple tables (${tables}) in schema "${schemaName}". Please drop indexes manually or ensure unique index names.`
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
const tableName = result.recordset[0].table_name;
|
|
2036
|
+
const fullTableName = getTableName({
|
|
2037
|
+
indexName: tableName,
|
|
2038
|
+
schemaName: getSchemaName(this.schemaName)
|
|
2039
|
+
});
|
|
2040
|
+
const dropSql = `DROP INDEX [${indexNameSafe}] ON ${fullTableName}`;
|
|
2041
|
+
await this.pool.request().query(dropSql);
|
|
2042
|
+
} catch (error) {
|
|
2043
|
+
throw new MastraError(
|
|
2044
|
+
{
|
|
2045
|
+
id: createStorageErrorId("MSSQL", "INDEX_DROP", "FAILED"),
|
|
2046
|
+
domain: ErrorDomain.STORAGE,
|
|
2047
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2048
|
+
details: {
|
|
2049
|
+
indexName
|
|
2050
|
+
}
|
|
2051
|
+
},
|
|
2052
|
+
error
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
/**
|
|
2057
|
+
* List indexes for a specific table or all tables
|
|
2058
|
+
*/
|
|
2059
|
+
async listIndexes(tableName) {
|
|
2060
|
+
try {
|
|
2061
|
+
const schemaName = this.schemaName || "dbo";
|
|
2062
|
+
let query;
|
|
2063
|
+
const request = this.pool.request();
|
|
2064
|
+
request.input("schemaName", schemaName);
|
|
2065
|
+
if (tableName) {
|
|
2066
|
+
query = `
|
|
2067
|
+
SELECT
|
|
2068
|
+
i.name as name,
|
|
2069
|
+
o.name as [table],
|
|
2070
|
+
i.is_unique as is_unique,
|
|
2071
|
+
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
|
|
2072
|
+
FROM sys.indexes i
|
|
2073
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2074
|
+
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2075
|
+
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2076
|
+
WHERE sch.name = @schemaName
|
|
2077
|
+
AND o.name = @tableName
|
|
2078
|
+
AND i.name IS NOT NULL
|
|
2079
|
+
GROUP BY i.name, o.name, i.is_unique
|
|
2080
|
+
`;
|
|
2081
|
+
request.input("tableName", tableName);
|
|
2082
|
+
} else {
|
|
2083
|
+
query = `
|
|
2084
|
+
SELECT
|
|
2085
|
+
i.name as name,
|
|
2086
|
+
o.name as [table],
|
|
2087
|
+
i.is_unique as is_unique,
|
|
2088
|
+
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
|
|
2089
|
+
FROM sys.indexes i
|
|
2090
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2091
|
+
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2092
|
+
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2093
|
+
WHERE sch.name = @schemaName
|
|
2094
|
+
AND i.name IS NOT NULL
|
|
2095
|
+
GROUP BY i.name, o.name, i.is_unique
|
|
2096
|
+
`;
|
|
2097
|
+
}
|
|
2098
|
+
const result = await request.query(query);
|
|
2099
|
+
const indexes = [];
|
|
2100
|
+
for (const row of result.recordset) {
|
|
2101
|
+
const colRequest = this.pool.request();
|
|
2102
|
+
colRequest.input("indexName", row.name);
|
|
2103
|
+
colRequest.input("schemaName", schemaName);
|
|
2104
|
+
const colResult = await colRequest.query(`
|
|
2105
|
+
SELECT c.name as column_name
|
|
2106
|
+
FROM sys.indexes i
|
|
2107
|
+
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
2108
|
+
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
2109
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2110
|
+
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
|
2111
|
+
WHERE i.name = @indexName
|
|
2112
|
+
AND s.name = @schemaName
|
|
2113
|
+
ORDER BY ic.key_ordinal
|
|
2114
|
+
`);
|
|
2115
|
+
indexes.push({
|
|
2116
|
+
name: row.name,
|
|
2117
|
+
table: row.table,
|
|
2118
|
+
columns: colResult.recordset.map((c) => c.column_name),
|
|
2119
|
+
unique: row.is_unique || false,
|
|
2120
|
+
size: row.size || "0 MB",
|
|
2121
|
+
definition: ""
|
|
2122
|
+
// MSSQL doesn't store definition like PG
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
return indexes;
|
|
2126
|
+
} catch (error) {
|
|
2127
|
+
throw new MastraError(
|
|
2128
|
+
{
|
|
2129
|
+
id: createStorageErrorId("MSSQL", "INDEX_LIST", "FAILED"),
|
|
2130
|
+
domain: ErrorDomain.STORAGE,
|
|
2131
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2132
|
+
details: tableName ? {
|
|
2133
|
+
tableName
|
|
2134
|
+
} : {}
|
|
2135
|
+
},
|
|
2136
|
+
error
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Get detailed statistics for a specific index
|
|
2142
|
+
*/
|
|
2143
|
+
async describeIndex(indexName) {
|
|
2144
|
+
try {
|
|
2145
|
+
const schemaName = this.schemaName || "dbo";
|
|
2146
|
+
const request = this.pool.request();
|
|
2147
|
+
request.input("indexName", indexName);
|
|
2148
|
+
request.input("schemaName", schemaName);
|
|
2149
|
+
const query = `
|
|
2150
|
+
SELECT
|
|
2151
|
+
i.name as name,
|
|
2152
|
+
o.name as [table],
|
|
2153
|
+
i.is_unique as is_unique,
|
|
2154
|
+
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size,
|
|
2155
|
+
i.type_desc as method,
|
|
2156
|
+
ISNULL(us.user_scans, 0) as scans,
|
|
2157
|
+
ISNULL(us.user_seeks + us.user_scans, 0) as tuples_read,
|
|
2158
|
+
ISNULL(us.user_lookups, 0) as tuples_fetched
|
|
2159
|
+
FROM sys.indexes i
|
|
2160
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2161
|
+
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2162
|
+
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2163
|
+
LEFT JOIN sys.dm_db_index_usage_stats us ON i.object_id = us.object_id AND i.index_id = us.index_id
|
|
2164
|
+
WHERE i.name = @indexName
|
|
2165
|
+
AND sch.name = @schemaName
|
|
2166
|
+
GROUP BY i.name, o.name, i.is_unique, i.type_desc, us.user_seeks, us.user_scans, us.user_lookups
|
|
2167
|
+
`;
|
|
2168
|
+
const result = await request.query(query);
|
|
2169
|
+
if (!result.recordset || result.recordset.length === 0) {
|
|
2170
|
+
throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
|
|
2171
|
+
}
|
|
2172
|
+
const row = result.recordset[0];
|
|
2173
|
+
const colRequest = this.pool.request();
|
|
2174
|
+
colRequest.input("indexName", indexName);
|
|
2175
|
+
colRequest.input("schemaName", schemaName);
|
|
2176
|
+
const colResult = await colRequest.query(`
|
|
2177
|
+
SELECT c.name as column_name
|
|
2178
|
+
FROM sys.indexes i
|
|
2179
|
+
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
2180
|
+
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
2181
|
+
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2182
|
+
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
|
2183
|
+
WHERE i.name = @indexName
|
|
2184
|
+
AND s.name = @schemaName
|
|
2185
|
+
ORDER BY ic.key_ordinal
|
|
2186
|
+
`);
|
|
2187
|
+
return {
|
|
2188
|
+
name: row.name,
|
|
2189
|
+
table: row.table,
|
|
2190
|
+
columns: colResult.recordset.map((c) => c.column_name),
|
|
2191
|
+
unique: row.is_unique || false,
|
|
2192
|
+
size: row.size || "0 MB",
|
|
2193
|
+
definition: "",
|
|
2194
|
+
method: row.method?.toLowerCase() || "nonclustered",
|
|
2195
|
+
scans: Number(row.scans) || 0,
|
|
2196
|
+
tuples_read: Number(row.tuples_read) || 0,
|
|
2197
|
+
tuples_fetched: Number(row.tuples_fetched) || 0
|
|
2198
|
+
};
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
throw new MastraError(
|
|
2201
|
+
{
|
|
2202
|
+
id: createStorageErrorId("MSSQL", "INDEX_DESCRIBE", "FAILED"),
|
|
2203
|
+
domain: ErrorDomain.STORAGE,
|
|
2204
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2205
|
+
details: {
|
|
2206
|
+
indexName
|
|
2207
|
+
}
|
|
2208
|
+
},
|
|
2209
|
+
error
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Returns definitions for automatic performance indexes
|
|
2215
|
+
* IMPORTANT: Uses seq_id DESC instead of createdAt DESC for MSSQL due to millisecond accuracy limitations
|
|
2216
|
+
* NOTE: Using NVARCHAR(400) for text columns (800 bytes) leaves room for composite indexes
|
|
2217
|
+
*/
|
|
2218
|
+
getAutomaticIndexDefinitions() {
|
|
2219
|
+
const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
|
|
2220
|
+
return [
|
|
2221
|
+
// Composite indexes for optimal filtering + sorting performance
|
|
2222
|
+
// NVARCHAR(400) = 800 bytes, plus BIGINT (8 bytes) = 808 bytes total (under 900-byte limit)
|
|
2223
|
+
{
|
|
2224
|
+
name: `${schemaPrefix}mastra_threads_resourceid_seqid_idx`,
|
|
2225
|
+
table: TABLE_THREADS,
|
|
2226
|
+
columns: ["resourceId", "seq_id DESC"]
|
|
2227
|
+
},
|
|
2228
|
+
{
|
|
2229
|
+
name: `${schemaPrefix}mastra_messages_thread_id_seqid_idx`,
|
|
2230
|
+
table: TABLE_MESSAGES,
|
|
2231
|
+
columns: ["thread_id", "seq_id DESC"]
|
|
2232
|
+
},
|
|
2233
|
+
{
|
|
2234
|
+
name: `${schemaPrefix}mastra_traces_name_seqid_idx`,
|
|
2235
|
+
table: TABLE_TRACES,
|
|
2236
|
+
columns: ["name", "seq_id DESC"]
|
|
2237
|
+
},
|
|
2238
|
+
{
|
|
2239
|
+
name: `${schemaPrefix}mastra_scores_trace_id_span_id_seqid_idx`,
|
|
2240
|
+
table: TABLE_SCORERS,
|
|
2241
|
+
columns: ["traceId", "spanId", "seq_id DESC"]
|
|
2242
|
+
},
|
|
2243
|
+
// Spans indexes for optimal trace querying
|
|
2244
|
+
{
|
|
2245
|
+
name: `${schemaPrefix}mastra_ai_spans_traceid_startedat_idx`,
|
|
2246
|
+
table: TABLE_SPANS,
|
|
2247
|
+
columns: ["traceId", "startedAt DESC"]
|
|
2248
|
+
},
|
|
2249
|
+
{
|
|
2250
|
+
name: `${schemaPrefix}mastra_ai_spans_parentspanid_startedat_idx`,
|
|
2251
|
+
table: TABLE_SPANS,
|
|
2252
|
+
columns: ["parentSpanId", "startedAt DESC"]
|
|
2253
|
+
},
|
|
2254
|
+
{
|
|
2255
|
+
name: `${schemaPrefix}mastra_ai_spans_name_idx`,
|
|
2256
|
+
table: TABLE_SPANS,
|
|
2257
|
+
columns: ["name"]
|
|
2258
|
+
},
|
|
2259
|
+
{
|
|
2260
|
+
name: `${schemaPrefix}mastra_ai_spans_spantype_startedat_idx`,
|
|
2261
|
+
table: TABLE_SPANS,
|
|
2262
|
+
columns: ["spanType", "startedAt DESC"]
|
|
2263
|
+
}
|
|
2264
|
+
];
|
|
2265
|
+
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Creates automatic indexes for optimal query performance
|
|
2268
|
+
* Uses getAutomaticIndexDefinitions() to determine which indexes to create
|
|
2269
|
+
*/
|
|
2270
|
+
async createAutomaticIndexes() {
|
|
2271
|
+
try {
|
|
2272
|
+
const indexes = this.getAutomaticIndexDefinitions();
|
|
2273
|
+
for (const indexOptions of indexes) {
|
|
2274
|
+
try {
|
|
2275
|
+
await this.createIndex(indexOptions);
|
|
2276
|
+
} catch (error) {
|
|
2277
|
+
this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
} catch (error) {
|
|
2281
|
+
throw new MastraError(
|
|
2282
|
+
{
|
|
2283
|
+
id: createStorageErrorId("MSSQL", "CREATE_PERFORMANCE_INDEXES", "FAILED"),
|
|
2284
|
+
domain: ErrorDomain.STORAGE,
|
|
2285
|
+
category: ErrorCategory.THIRD_PARTY
|
|
2286
|
+
},
|
|
2287
|
+
error
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
1422
2292
|
function transformScoreRow(row) {
|
|
1423
|
-
return {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
scorer: parseJSON(row.scorer),
|
|
1427
|
-
preprocessStepResult: parseJSON(row.preprocessStepResult),
|
|
1428
|
-
analyzeStepResult: parseJSON(row.analyzeStepResult),
|
|
1429
|
-
metadata: parseJSON(row.metadata),
|
|
1430
|
-
output: parseJSON(row.output),
|
|
1431
|
-
additionalContext: parseJSON(row.additionalContext),
|
|
1432
|
-
runtimeContext: parseJSON(row.runtimeContext),
|
|
1433
|
-
entity: parseJSON(row.entity),
|
|
1434
|
-
createdAt: row.createdAt,
|
|
1435
|
-
updatedAt: row.updatedAt
|
|
1436
|
-
};
|
|
2293
|
+
return transformScoreRow$1(row, {
|
|
2294
|
+
convertTimestamps: true
|
|
2295
|
+
});
|
|
1437
2296
|
}
|
|
1438
2297
|
var ScoresMSSQL = class extends ScoresStorage {
|
|
1439
2298
|
pool;
|
|
@@ -1463,7 +2322,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1463
2322
|
} catch (error) {
|
|
1464
2323
|
throw new MastraError(
|
|
1465
2324
|
{
|
|
1466
|
-
id: "
|
|
2325
|
+
id: createStorageErrorId("MSSQL", "GET_SCORE_BY_ID", "FAILED"),
|
|
1467
2326
|
domain: ErrorDomain.STORAGE,
|
|
1468
2327
|
category: ErrorCategory.THIRD_PARTY,
|
|
1469
2328
|
details: { id }
|
|
@@ -1471,10 +2330,31 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1471
2330
|
error
|
|
1472
2331
|
);
|
|
1473
2332
|
}
|
|
1474
|
-
}
|
|
1475
|
-
async saveScore(score) {
|
|
2333
|
+
}
|
|
2334
|
+
async saveScore(score) {
|
|
2335
|
+
let validatedScore;
|
|
2336
|
+
try {
|
|
2337
|
+
validatedScore = saveScorePayloadSchema.parse(score);
|
|
2338
|
+
} catch (error) {
|
|
2339
|
+
throw new MastraError(
|
|
2340
|
+
{
|
|
2341
|
+
id: createStorageErrorId("MSSQL", "SAVE_SCORE", "VALIDATION_FAILED"),
|
|
2342
|
+
domain: ErrorDomain.STORAGE,
|
|
2343
|
+
category: ErrorCategory.USER,
|
|
2344
|
+
details: {
|
|
2345
|
+
scorer: score.scorer?.id ?? "unknown",
|
|
2346
|
+
entityId: score.entityId ?? "unknown",
|
|
2347
|
+
entityType: score.entityType ?? "unknown",
|
|
2348
|
+
traceId: score.traceId ?? "",
|
|
2349
|
+
spanId: score.spanId ?? ""
|
|
2350
|
+
}
|
|
2351
|
+
},
|
|
2352
|
+
error
|
|
2353
|
+
);
|
|
2354
|
+
}
|
|
1476
2355
|
try {
|
|
1477
|
-
const scoreId =
|
|
2356
|
+
const scoreId = randomUUID();
|
|
2357
|
+
const now = /* @__PURE__ */ new Date();
|
|
1478
2358
|
const {
|
|
1479
2359
|
scorer,
|
|
1480
2360
|
preprocessStepResult,
|
|
@@ -1483,34 +2363,33 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1483
2363
|
input,
|
|
1484
2364
|
output,
|
|
1485
2365
|
additionalContext,
|
|
1486
|
-
|
|
2366
|
+
requestContext,
|
|
1487
2367
|
entity,
|
|
1488
2368
|
...rest
|
|
1489
|
-
} =
|
|
2369
|
+
} = validatedScore;
|
|
1490
2370
|
await this.operations.insert({
|
|
1491
2371
|
tableName: TABLE_SCORERS,
|
|
1492
2372
|
record: {
|
|
1493
2373
|
id: scoreId,
|
|
1494
2374
|
...rest,
|
|
1495
|
-
input:
|
|
1496
|
-
output:
|
|
1497
|
-
preprocessStepResult: preprocessStepResult
|
|
1498
|
-
analyzeStepResult: analyzeStepResult
|
|
1499
|
-
metadata: metadata
|
|
1500
|
-
additionalContext: additionalContext
|
|
1501
|
-
|
|
1502
|
-
entity: entity
|
|
1503
|
-
scorer: scorer
|
|
1504
|
-
createdAt:
|
|
1505
|
-
updatedAt:
|
|
2375
|
+
input: input || "",
|
|
2376
|
+
output: output || "",
|
|
2377
|
+
preprocessStepResult: preprocessStepResult || null,
|
|
2378
|
+
analyzeStepResult: analyzeStepResult || null,
|
|
2379
|
+
metadata: metadata || null,
|
|
2380
|
+
additionalContext: additionalContext || null,
|
|
2381
|
+
requestContext: requestContext || null,
|
|
2382
|
+
entity: entity || null,
|
|
2383
|
+
scorer: scorer || null,
|
|
2384
|
+
createdAt: now.toISOString(),
|
|
2385
|
+
updatedAt: now.toISOString()
|
|
1506
2386
|
}
|
|
1507
2387
|
});
|
|
1508
|
-
|
|
1509
|
-
return { score: scoreFromDb };
|
|
2388
|
+
return { score: { ...validatedScore, id: scoreId, createdAt: now, updatedAt: now } };
|
|
1510
2389
|
} catch (error) {
|
|
1511
2390
|
throw new MastraError(
|
|
1512
2391
|
{
|
|
1513
|
-
id: "
|
|
2392
|
+
id: createStorageErrorId("MSSQL", "SAVE_SCORE", "FAILED"),
|
|
1514
2393
|
domain: ErrorDomain.STORAGE,
|
|
1515
2394
|
category: ErrorCategory.THIRD_PARTY
|
|
1516
2395
|
},
|
|
@@ -1518,48 +2397,77 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1518
2397
|
);
|
|
1519
2398
|
}
|
|
1520
2399
|
}
|
|
1521
|
-
async
|
|
2400
|
+
async listScoresByScorerId({
|
|
1522
2401
|
scorerId,
|
|
1523
|
-
pagination
|
|
2402
|
+
pagination,
|
|
2403
|
+
entityId,
|
|
2404
|
+
entityType,
|
|
2405
|
+
source
|
|
1524
2406
|
}) {
|
|
1525
2407
|
try {
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
2408
|
+
const conditions = ["[scorerId] = @p1"];
|
|
2409
|
+
const params = { p1: scorerId };
|
|
2410
|
+
let paramIndex = 2;
|
|
2411
|
+
if (entityId) {
|
|
2412
|
+
conditions.push(`[entityId] = @p${paramIndex}`);
|
|
2413
|
+
params[`p${paramIndex}`] = entityId;
|
|
2414
|
+
paramIndex++;
|
|
2415
|
+
}
|
|
2416
|
+
if (entityType) {
|
|
2417
|
+
conditions.push(`[entityType] = @p${paramIndex}`);
|
|
2418
|
+
params[`p${paramIndex}`] = entityType;
|
|
2419
|
+
paramIndex++;
|
|
2420
|
+
}
|
|
2421
|
+
if (source) {
|
|
2422
|
+
conditions.push(`[source] = @p${paramIndex}`);
|
|
2423
|
+
params[`p${paramIndex}`] = source;
|
|
2424
|
+
paramIndex++;
|
|
2425
|
+
}
|
|
2426
|
+
const whereClause = conditions.join(" AND ");
|
|
2427
|
+
const tableName = getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) });
|
|
2428
|
+
const countRequest = this.pool.request();
|
|
2429
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
2430
|
+
countRequest.input(key, value);
|
|
2431
|
+
});
|
|
2432
|
+
const totalResult = await countRequest.query(`SELECT COUNT(*) as count FROM ${tableName} WHERE ${whereClause}`);
|
|
1531
2433
|
const total = totalResult.recordset[0]?.count || 0;
|
|
2434
|
+
const { page, perPage: perPageInput } = pagination;
|
|
1532
2435
|
if (total === 0) {
|
|
1533
2436
|
return {
|
|
1534
2437
|
pagination: {
|
|
1535
2438
|
total: 0,
|
|
1536
|
-
page
|
|
1537
|
-
perPage:
|
|
2439
|
+
page,
|
|
2440
|
+
perPage: perPageInput,
|
|
1538
2441
|
hasMore: false
|
|
1539
2442
|
},
|
|
1540
2443
|
scores: []
|
|
1541
2444
|
};
|
|
1542
2445
|
}
|
|
2446
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
2447
|
+
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
2448
|
+
const limitValue = perPageInput === false ? total : perPage;
|
|
2449
|
+
const end = perPageInput === false ? total : start + perPage;
|
|
1543
2450
|
const dataRequest = this.pool.request();
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
2451
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
2452
|
+
dataRequest.input(key, value);
|
|
2453
|
+
});
|
|
2454
|
+
dataRequest.input("perPage", limitValue);
|
|
2455
|
+
dataRequest.input("offset", start);
|
|
2456
|
+
const dataQuery = `SELECT * FROM ${tableName} WHERE ${whereClause} ORDER BY [createdAt] DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
2457
|
+
const result = await dataRequest.query(dataQuery);
|
|
1550
2458
|
return {
|
|
1551
2459
|
pagination: {
|
|
1552
2460
|
total: Number(total),
|
|
1553
|
-
page
|
|
1554
|
-
perPage:
|
|
1555
|
-
hasMore:
|
|
2461
|
+
page,
|
|
2462
|
+
perPage: perPageForResponse,
|
|
2463
|
+
hasMore: end < total
|
|
1556
2464
|
},
|
|
1557
2465
|
scores: result.recordset.map((row) => transformScoreRow(row))
|
|
1558
2466
|
};
|
|
1559
2467
|
} catch (error) {
|
|
1560
2468
|
throw new MastraError(
|
|
1561
2469
|
{
|
|
1562
|
-
id: "
|
|
2470
|
+
id: createStorageErrorId("MSSQL", "LIST_SCORES_BY_SCORER_ID", "FAILED"),
|
|
1563
2471
|
domain: ErrorDomain.STORAGE,
|
|
1564
2472
|
category: ErrorCategory.THIRD_PARTY,
|
|
1565
2473
|
details: { scorerId }
|
|
@@ -1568,7 +2476,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1568
2476
|
);
|
|
1569
2477
|
}
|
|
1570
2478
|
}
|
|
1571
|
-
async
|
|
2479
|
+
async listScoresByRunId({
|
|
1572
2480
|
runId,
|
|
1573
2481
|
pagination
|
|
1574
2482
|
}) {
|
|
@@ -1579,37 +2487,42 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1579
2487
|
`SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1`
|
|
1580
2488
|
);
|
|
1581
2489
|
const total = totalResult.recordset[0]?.count || 0;
|
|
2490
|
+
const { page, perPage: perPageInput } = pagination;
|
|
1582
2491
|
if (total === 0) {
|
|
1583
2492
|
return {
|
|
1584
2493
|
pagination: {
|
|
1585
2494
|
total: 0,
|
|
1586
|
-
page
|
|
1587
|
-
perPage:
|
|
2495
|
+
page,
|
|
2496
|
+
perPage: perPageInput,
|
|
1588
2497
|
hasMore: false
|
|
1589
2498
|
},
|
|
1590
2499
|
scores: []
|
|
1591
2500
|
};
|
|
1592
2501
|
}
|
|
2502
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
2503
|
+
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
2504
|
+
const limitValue = perPageInput === false ? total : perPage;
|
|
2505
|
+
const end = perPageInput === false ? total : start + perPage;
|
|
1593
2506
|
const dataRequest = this.pool.request();
|
|
1594
2507
|
dataRequest.input("p1", runId);
|
|
1595
|
-
dataRequest.input("p2",
|
|
1596
|
-
dataRequest.input("p3",
|
|
2508
|
+
dataRequest.input("p2", limitValue);
|
|
2509
|
+
dataRequest.input("p3", start);
|
|
1597
2510
|
const result = await dataRequest.query(
|
|
1598
2511
|
`SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1 ORDER BY [createdAt] DESC OFFSET @p3 ROWS FETCH NEXT @p2 ROWS ONLY`
|
|
1599
2512
|
);
|
|
1600
2513
|
return {
|
|
1601
2514
|
pagination: {
|
|
1602
2515
|
total: Number(total),
|
|
1603
|
-
page
|
|
1604
|
-
perPage:
|
|
1605
|
-
hasMore:
|
|
2516
|
+
page,
|
|
2517
|
+
perPage: perPageForResponse,
|
|
2518
|
+
hasMore: end < total
|
|
1606
2519
|
},
|
|
1607
2520
|
scores: result.recordset.map((row) => transformScoreRow(row))
|
|
1608
2521
|
};
|
|
1609
2522
|
} catch (error) {
|
|
1610
2523
|
throw new MastraError(
|
|
1611
2524
|
{
|
|
1612
|
-
id: "
|
|
2525
|
+
id: createStorageErrorId("MSSQL", "LIST_SCORES_BY_RUN_ID", "FAILED"),
|
|
1613
2526
|
domain: ErrorDomain.STORAGE,
|
|
1614
2527
|
category: ErrorCategory.THIRD_PARTY,
|
|
1615
2528
|
details: { runId }
|
|
@@ -1618,7 +2531,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1618
2531
|
);
|
|
1619
2532
|
}
|
|
1620
2533
|
}
|
|
1621
|
-
async
|
|
2534
|
+
async listScoresByEntityId({
|
|
1622
2535
|
entityId,
|
|
1623
2536
|
entityType,
|
|
1624
2537
|
pagination
|
|
@@ -1631,38 +2544,43 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1631
2544
|
`SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2`
|
|
1632
2545
|
);
|
|
1633
2546
|
const total = totalResult.recordset[0]?.count || 0;
|
|
2547
|
+
const { page, perPage: perPageInput } = pagination;
|
|
2548
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
2549
|
+
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1634
2550
|
if (total === 0) {
|
|
1635
2551
|
return {
|
|
1636
2552
|
pagination: {
|
|
1637
2553
|
total: 0,
|
|
1638
|
-
page
|
|
1639
|
-
perPage:
|
|
2554
|
+
page,
|
|
2555
|
+
perPage: perPageForResponse,
|
|
1640
2556
|
hasMore: false
|
|
1641
2557
|
},
|
|
1642
2558
|
scores: []
|
|
1643
2559
|
};
|
|
1644
2560
|
}
|
|
2561
|
+
const limitValue = perPageInput === false ? total : perPage;
|
|
2562
|
+
const end = perPageInput === false ? total : start + perPage;
|
|
1645
2563
|
const dataRequest = this.pool.request();
|
|
1646
2564
|
dataRequest.input("p1", entityId);
|
|
1647
2565
|
dataRequest.input("p2", entityType);
|
|
1648
|
-
dataRequest.input("p3",
|
|
1649
|
-
dataRequest.input("p4",
|
|
2566
|
+
dataRequest.input("p3", limitValue);
|
|
2567
|
+
dataRequest.input("p4", start);
|
|
1650
2568
|
const result = await dataRequest.query(
|
|
1651
2569
|
`SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
|
|
1652
2570
|
);
|
|
1653
2571
|
return {
|
|
1654
2572
|
pagination: {
|
|
1655
2573
|
total: Number(total),
|
|
1656
|
-
page
|
|
1657
|
-
perPage:
|
|
1658
|
-
hasMore:
|
|
2574
|
+
page,
|
|
2575
|
+
perPage: perPageForResponse,
|
|
2576
|
+
hasMore: end < total
|
|
1659
2577
|
},
|
|
1660
2578
|
scores: result.recordset.map((row) => transformScoreRow(row))
|
|
1661
2579
|
};
|
|
1662
2580
|
} catch (error) {
|
|
1663
2581
|
throw new MastraError(
|
|
1664
2582
|
{
|
|
1665
|
-
id: "
|
|
2583
|
+
id: createStorageErrorId("MSSQL", "LIST_SCORES_BY_ENTITY_ID", "FAILED"),
|
|
1666
2584
|
domain: ErrorDomain.STORAGE,
|
|
1667
2585
|
category: ErrorCategory.THIRD_PARTY,
|
|
1668
2586
|
details: { entityId, entityType }
|
|
@@ -1671,8 +2589,66 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
1671
2589
|
);
|
|
1672
2590
|
}
|
|
1673
2591
|
}
|
|
2592
|
+
async listScoresBySpan({
|
|
2593
|
+
traceId,
|
|
2594
|
+
spanId,
|
|
2595
|
+
pagination
|
|
2596
|
+
}) {
|
|
2597
|
+
try {
|
|
2598
|
+
const request = this.pool.request();
|
|
2599
|
+
request.input("p1", traceId);
|
|
2600
|
+
request.input("p2", spanId);
|
|
2601
|
+
const totalResult = await request.query(
|
|
2602
|
+
`SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2`
|
|
2603
|
+
);
|
|
2604
|
+
const total = totalResult.recordset[0]?.count || 0;
|
|
2605
|
+
const { page, perPage: perPageInput } = pagination;
|
|
2606
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
2607
|
+
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
2608
|
+
if (total === 0) {
|
|
2609
|
+
return {
|
|
2610
|
+
pagination: {
|
|
2611
|
+
total: 0,
|
|
2612
|
+
page,
|
|
2613
|
+
perPage: perPageForResponse,
|
|
2614
|
+
hasMore: false
|
|
2615
|
+
},
|
|
2616
|
+
scores: []
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
const limitValue = perPageInput === false ? total : perPage;
|
|
2620
|
+
const end = perPageInput === false ? total : start + perPage;
|
|
2621
|
+
const dataRequest = this.pool.request();
|
|
2622
|
+
dataRequest.input("p1", traceId);
|
|
2623
|
+
dataRequest.input("p2", spanId);
|
|
2624
|
+
dataRequest.input("p3", limitValue);
|
|
2625
|
+
dataRequest.input("p4", start);
|
|
2626
|
+
const result = await dataRequest.query(
|
|
2627
|
+
`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`
|
|
2628
|
+
);
|
|
2629
|
+
return {
|
|
2630
|
+
pagination: {
|
|
2631
|
+
total: Number(total),
|
|
2632
|
+
page,
|
|
2633
|
+
perPage: perPageForResponse,
|
|
2634
|
+
hasMore: end < total
|
|
2635
|
+
},
|
|
2636
|
+
scores: result.recordset.map((row) => transformScoreRow(row))
|
|
2637
|
+
};
|
|
2638
|
+
} catch (error) {
|
|
2639
|
+
throw new MastraError(
|
|
2640
|
+
{
|
|
2641
|
+
id: createStorageErrorId("MSSQL", "LIST_SCORES_BY_SPAN", "FAILED"),
|
|
2642
|
+
domain: ErrorDomain.STORAGE,
|
|
2643
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2644
|
+
details: { traceId, spanId }
|
|
2645
|
+
},
|
|
2646
|
+
error
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
1674
2650
|
};
|
|
1675
|
-
var
|
|
2651
|
+
var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
1676
2652
|
pool;
|
|
1677
2653
|
operations;
|
|
1678
2654
|
schema;
|
|
@@ -1686,207 +2662,166 @@ var TracesMSSQL = class extends TracesStorage {
|
|
|
1686
2662
|
this.operations = operations;
|
|
1687
2663
|
this.schema = schema;
|
|
1688
2664
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
if (
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
2665
|
+
parseWorkflowRun(row) {
|
|
2666
|
+
let parsedSnapshot = row.snapshot;
|
|
2667
|
+
if (typeof parsedSnapshot === "string") {
|
|
2668
|
+
try {
|
|
2669
|
+
parsedSnapshot = JSON.parse(row.snapshot);
|
|
2670
|
+
} catch (e) {
|
|
2671
|
+
this.logger?.warn?.(`Failed to parse snapshot for workflow ${row.workflow_name}:`, e);
|
|
2672
|
+
}
|
|
1696
2673
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
2674
|
+
return {
|
|
2675
|
+
workflowName: row.workflow_name,
|
|
2676
|
+
runId: row.run_id,
|
|
2677
|
+
snapshot: parsedSnapshot,
|
|
2678
|
+
createdAt: row.createdAt,
|
|
2679
|
+
updatedAt: row.updatedAt,
|
|
2680
|
+
resourceId: row.resourceId
|
|
2681
|
+
};
|
|
1699
2682
|
}
|
|
1700
|
-
async
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
const
|
|
1708
|
-
|
|
1709
|
-
if (name) {
|
|
1710
|
-
const paramName = `p${paramIndex++}`;
|
|
1711
|
-
conditions.push(`[name] LIKE @${paramName}`);
|
|
1712
|
-
paramMap[paramName] = `${name}%`;
|
|
1713
|
-
}
|
|
1714
|
-
if (scope) {
|
|
1715
|
-
const paramName = `p${paramIndex++}`;
|
|
1716
|
-
conditions.push(`[scope] = @${paramName}`);
|
|
1717
|
-
paramMap[paramName] = scope;
|
|
1718
|
-
}
|
|
1719
|
-
if (attributes) {
|
|
1720
|
-
Object.entries(attributes).forEach(([key, value]) => {
|
|
1721
|
-
const parsedKey = parseFieldKey(key);
|
|
1722
|
-
const paramName = `p${paramIndex++}`;
|
|
1723
|
-
conditions.push(`JSON_VALUE([attributes], '$.${parsedKey}') = @${paramName}`);
|
|
1724
|
-
paramMap[paramName] = value;
|
|
1725
|
-
});
|
|
1726
|
-
}
|
|
1727
|
-
if (filters) {
|
|
1728
|
-
Object.entries(filters).forEach(([key, value]) => {
|
|
1729
|
-
const parsedKey = parseFieldKey(key);
|
|
1730
|
-
const paramName = `p${paramIndex++}`;
|
|
1731
|
-
conditions.push(`[${parsedKey}] = @${paramName}`);
|
|
1732
|
-
paramMap[paramName] = value;
|
|
1733
|
-
});
|
|
1734
|
-
}
|
|
1735
|
-
if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
|
|
1736
|
-
const paramName = `p${paramIndex++}`;
|
|
1737
|
-
conditions.push(`[createdAt] >= @${paramName}`);
|
|
1738
|
-
paramMap[paramName] = fromDate.toISOString();
|
|
1739
|
-
}
|
|
1740
|
-
if (toDate instanceof Date && !isNaN(toDate.getTime())) {
|
|
1741
|
-
const paramName = `p${paramIndex++}`;
|
|
1742
|
-
conditions.push(`[createdAt] <= @${paramName}`);
|
|
1743
|
-
paramMap[paramName] = toDate.toISOString();
|
|
1744
|
-
}
|
|
1745
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1746
|
-
const countQuery = `SELECT COUNT(*) as total FROM ${getTableName({ indexName: TABLE_TRACES, schemaName: getSchemaName(this.schema) })} ${whereClause}`;
|
|
1747
|
-
let total = 0;
|
|
2683
|
+
async updateWorkflowResults({
|
|
2684
|
+
workflowName,
|
|
2685
|
+
runId,
|
|
2686
|
+
stepId,
|
|
2687
|
+
result,
|
|
2688
|
+
requestContext
|
|
2689
|
+
}) {
|
|
2690
|
+
const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
2691
|
+
const transaction = this.pool.transaction();
|
|
1748
2692
|
try {
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
2693
|
+
await transaction.begin();
|
|
2694
|
+
const selectRequest = new sql3.Request(transaction);
|
|
2695
|
+
selectRequest.input("workflow_name", workflowName);
|
|
2696
|
+
selectRequest.input("run_id", runId);
|
|
2697
|
+
const existingSnapshotResult = await selectRequest.query(
|
|
2698
|
+
`SELECT snapshot FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
2699
|
+
);
|
|
2700
|
+
let snapshot;
|
|
2701
|
+
if (!existingSnapshotResult.recordset || existingSnapshotResult.recordset.length === 0) {
|
|
2702
|
+
snapshot = {
|
|
2703
|
+
context: {},
|
|
2704
|
+
activePaths: [],
|
|
2705
|
+
activeStepsPath: {},
|
|
2706
|
+
timestamp: Date.now(),
|
|
2707
|
+
suspendedPaths: {},
|
|
2708
|
+
resumeLabels: {},
|
|
2709
|
+
serializedStepGraph: [],
|
|
2710
|
+
status: "pending",
|
|
2711
|
+
value: {},
|
|
2712
|
+
waitingPaths: {},
|
|
2713
|
+
runId,
|
|
2714
|
+
requestContext: {}
|
|
2715
|
+
};
|
|
2716
|
+
} else {
|
|
2717
|
+
const existingSnapshot = existingSnapshotResult.recordset[0].snapshot;
|
|
2718
|
+
snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
|
|
2719
|
+
}
|
|
2720
|
+
snapshot.context[stepId] = result;
|
|
2721
|
+
snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
|
|
2722
|
+
const upsertReq = new sql3.Request(transaction);
|
|
2723
|
+
upsertReq.input("workflow_name", workflowName);
|
|
2724
|
+
upsertReq.input("run_id", runId);
|
|
2725
|
+
upsertReq.input("snapshot", JSON.stringify(snapshot));
|
|
2726
|
+
upsertReq.input("createdAt", sql3.DateTime2, /* @__PURE__ */ new Date());
|
|
2727
|
+
upsertReq.input("updatedAt", sql3.DateTime2, /* @__PURE__ */ new Date());
|
|
2728
|
+
await upsertReq.query(
|
|
2729
|
+
`MERGE ${table} AS target
|
|
2730
|
+
USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
|
|
2731
|
+
ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
|
|
2732
|
+
WHEN MATCHED THEN UPDATE SET snapshot = @snapshot, [updatedAt] = @updatedAt
|
|
2733
|
+
WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, snapshot, [createdAt], [updatedAt])
|
|
2734
|
+
VALUES (@workflow_name, @run_id, @snapshot, @createdAt, @updatedAt);`
|
|
2735
|
+
);
|
|
2736
|
+
await transaction.commit();
|
|
2737
|
+
return snapshot.context;
|
|
1759
2738
|
} catch (error) {
|
|
2739
|
+
try {
|
|
2740
|
+
await transaction.rollback();
|
|
2741
|
+
} catch {
|
|
2742
|
+
}
|
|
1760
2743
|
throw new MastraError(
|
|
1761
2744
|
{
|
|
1762
|
-
id: "
|
|
2745
|
+
id: createStorageErrorId("MSSQL", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
|
|
1763
2746
|
domain: ErrorDomain.STORAGE,
|
|
1764
2747
|
category: ErrorCategory.THIRD_PARTY,
|
|
1765
2748
|
details: {
|
|
1766
|
-
|
|
1767
|
-
|
|
2749
|
+
workflowName,
|
|
2750
|
+
runId,
|
|
2751
|
+
stepId
|
|
1768
2752
|
}
|
|
1769
2753
|
},
|
|
1770
2754
|
error
|
|
1771
2755
|
);
|
|
1772
2756
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
}
|
|
1782
|
-
const dataQuery = `SELECT * FROM ${getTableName({ indexName: TABLE_TRACES, schemaName: getSchemaName(this.schema) })} ${whereClause} ORDER BY [seq_id] DESC OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
|
|
1783
|
-
const dataRequest = this.pool.request();
|
|
1784
|
-
Object.entries(paramMap).forEach(([key, value]) => {
|
|
1785
|
-
if (value instanceof Date) {
|
|
1786
|
-
dataRequest.input(key, sql2.DateTime, value);
|
|
1787
|
-
} else {
|
|
1788
|
-
dataRequest.input(key, value);
|
|
1789
|
-
}
|
|
1790
|
-
});
|
|
1791
|
-
dataRequest.input("offset", currentOffset);
|
|
1792
|
-
dataRequest.input("limit", perPage);
|
|
2757
|
+
}
|
|
2758
|
+
async updateWorkflowState({
|
|
2759
|
+
workflowName,
|
|
2760
|
+
runId,
|
|
2761
|
+
opts
|
|
2762
|
+
}) {
|
|
2763
|
+
const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
2764
|
+
const transaction = this.pool.transaction();
|
|
1793
2765
|
try {
|
|
1794
|
-
|
|
1795
|
-
const
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2766
|
+
await transaction.begin();
|
|
2767
|
+
const selectRequest = new sql3.Request(transaction);
|
|
2768
|
+
selectRequest.input("workflow_name", workflowName);
|
|
2769
|
+
selectRequest.input("run_id", runId);
|
|
2770
|
+
const existingSnapshotResult = await selectRequest.query(
|
|
2771
|
+
`SELECT snapshot FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
2772
|
+
);
|
|
2773
|
+
if (!existingSnapshotResult.recordset || existingSnapshotResult.recordset.length === 0) {
|
|
2774
|
+
await transaction.rollback();
|
|
2775
|
+
return void 0;
|
|
2776
|
+
}
|
|
2777
|
+
const existingSnapshot = existingSnapshotResult.recordset[0].snapshot;
|
|
2778
|
+
const snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
|
|
2779
|
+
if (!snapshot || !snapshot?.context) {
|
|
2780
|
+
await transaction.rollback();
|
|
2781
|
+
throw new MastraError(
|
|
2782
|
+
{
|
|
2783
|
+
id: createStorageErrorId("MSSQL", "UPDATE_WORKFLOW_STATE", "SNAPSHOT_NOT_FOUND"),
|
|
2784
|
+
domain: ErrorDomain.STORAGE,
|
|
2785
|
+
category: ErrorCategory.SYSTEM,
|
|
2786
|
+
details: {
|
|
2787
|
+
workflowName,
|
|
2788
|
+
runId
|
|
2789
|
+
}
|
|
2790
|
+
},
|
|
2791
|
+
new Error(`Snapshot not found for runId ${runId}`)
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2794
|
+
const updatedSnapshot = { ...snapshot, ...opts };
|
|
2795
|
+
const updateRequest = new sql3.Request(transaction);
|
|
2796
|
+
updateRequest.input("snapshot", JSON.stringify(updatedSnapshot));
|
|
2797
|
+
updateRequest.input("workflow_name", workflowName);
|
|
2798
|
+
updateRequest.input("run_id", runId);
|
|
2799
|
+
updateRequest.input("updatedAt", sql3.DateTime2, /* @__PURE__ */ new Date());
|
|
2800
|
+
await updateRequest.query(
|
|
2801
|
+
`UPDATE ${table} SET snapshot = @snapshot, [updatedAt] = @updatedAt WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
2802
|
+
);
|
|
2803
|
+
await transaction.commit();
|
|
2804
|
+
return updatedSnapshot;
|
|
1819
2805
|
} catch (error) {
|
|
2806
|
+
try {
|
|
2807
|
+
await transaction.rollback();
|
|
2808
|
+
} catch {
|
|
2809
|
+
}
|
|
2810
|
+
if (error instanceof MastraError) throw error;
|
|
1820
2811
|
throw new MastraError(
|
|
1821
2812
|
{
|
|
1822
|
-
id: "
|
|
2813
|
+
id: createStorageErrorId("MSSQL", "UPDATE_WORKFLOW_STATE", "FAILED"),
|
|
1823
2814
|
domain: ErrorDomain.STORAGE,
|
|
1824
2815
|
category: ErrorCategory.THIRD_PARTY,
|
|
1825
2816
|
details: {
|
|
1826
|
-
|
|
1827
|
-
|
|
2817
|
+
workflowName,
|
|
2818
|
+
runId
|
|
1828
2819
|
}
|
|
1829
2820
|
},
|
|
1830
2821
|
error
|
|
1831
2822
|
);
|
|
1832
2823
|
}
|
|
1833
2824
|
}
|
|
1834
|
-
async batchTraceInsert({ records }) {
|
|
1835
|
-
this.logger.debug("Batch inserting traces", { count: records.length });
|
|
1836
|
-
await this.operations.batchInsert({
|
|
1837
|
-
tableName: TABLE_TRACES,
|
|
1838
|
-
records
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
};
|
|
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
|
-
var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
1861
|
-
pool;
|
|
1862
|
-
operations;
|
|
1863
|
-
schema;
|
|
1864
|
-
constructor({
|
|
1865
|
-
pool,
|
|
1866
|
-
operations,
|
|
1867
|
-
schema
|
|
1868
|
-
}) {
|
|
1869
|
-
super();
|
|
1870
|
-
this.pool = pool;
|
|
1871
|
-
this.operations = operations;
|
|
1872
|
-
this.schema = schema;
|
|
1873
|
-
}
|
|
1874
|
-
updateWorkflowResults({
|
|
1875
|
-
// workflowName,
|
|
1876
|
-
// runId,
|
|
1877
|
-
// stepId,
|
|
1878
|
-
// result,
|
|
1879
|
-
// runtimeContext,
|
|
1880
|
-
}) {
|
|
1881
|
-
throw new Error("Method not implemented.");
|
|
1882
|
-
}
|
|
1883
|
-
updateWorkflowState({
|
|
1884
|
-
// workflowName,
|
|
1885
|
-
// runId,
|
|
1886
|
-
// opts,
|
|
1887
|
-
}) {
|
|
1888
|
-
throw new Error("Method not implemented.");
|
|
1889
|
-
}
|
|
1890
2825
|
async persistWorkflowSnapshot({
|
|
1891
2826
|
workflowName,
|
|
1892
2827
|
runId,
|
|
@@ -1901,8 +2836,8 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1901
2836
|
request.input("run_id", runId);
|
|
1902
2837
|
request.input("resourceId", resourceId);
|
|
1903
2838
|
request.input("snapshot", JSON.stringify(snapshot));
|
|
1904
|
-
request.input("createdAt",
|
|
1905
|
-
request.input("updatedAt",
|
|
2839
|
+
request.input("createdAt", sql3.DateTime2, new Date(now));
|
|
2840
|
+
request.input("updatedAt", sql3.DateTime2, new Date(now));
|
|
1906
2841
|
const mergeSql = `MERGE INTO ${table} AS target
|
|
1907
2842
|
USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
|
|
1908
2843
|
ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
|
|
@@ -1916,7 +2851,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1916
2851
|
} catch (error) {
|
|
1917
2852
|
throw new MastraError(
|
|
1918
2853
|
{
|
|
1919
|
-
id: "
|
|
2854
|
+
id: createStorageErrorId("MSSQL", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
1920
2855
|
domain: ErrorDomain.STORAGE,
|
|
1921
2856
|
category: ErrorCategory.THIRD_PARTY,
|
|
1922
2857
|
details: {
|
|
@@ -1947,7 +2882,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1947
2882
|
} catch (error) {
|
|
1948
2883
|
throw new MastraError(
|
|
1949
2884
|
{
|
|
1950
|
-
id: "
|
|
2885
|
+
id: createStorageErrorId("MSSQL", "LOAD_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
1951
2886
|
domain: ErrorDomain.STORAGE,
|
|
1952
2887
|
category: ErrorCategory.THIRD_PARTY,
|
|
1953
2888
|
details: {
|
|
@@ -1983,11 +2918,11 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1983
2918
|
if (!result.recordset || result.recordset.length === 0) {
|
|
1984
2919
|
return null;
|
|
1985
2920
|
}
|
|
1986
|
-
return parseWorkflowRun(result.recordset[0]);
|
|
2921
|
+
return this.parseWorkflowRun(result.recordset[0]);
|
|
1987
2922
|
} catch (error) {
|
|
1988
2923
|
throw new MastraError(
|
|
1989
2924
|
{
|
|
1990
|
-
id: "
|
|
2925
|
+
id: createStorageErrorId("MSSQL", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
1991
2926
|
domain: ErrorDomain.STORAGE,
|
|
1992
2927
|
category: ErrorCategory.THIRD_PARTY,
|
|
1993
2928
|
details: {
|
|
@@ -1999,13 +2934,43 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
1999
2934
|
);
|
|
2000
2935
|
}
|
|
2001
2936
|
}
|
|
2002
|
-
async
|
|
2937
|
+
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
2938
|
+
const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
2939
|
+
const transaction = this.pool.transaction();
|
|
2940
|
+
try {
|
|
2941
|
+
await transaction.begin();
|
|
2942
|
+
const deleteRequest = new sql3.Request(transaction);
|
|
2943
|
+
deleteRequest.input("workflow_name", workflowName);
|
|
2944
|
+
deleteRequest.input("run_id", runId);
|
|
2945
|
+
await deleteRequest.query(`DELETE FROM ${table} WHERE workflow_name = @workflow_name AND run_id = @run_id`);
|
|
2946
|
+
await transaction.commit();
|
|
2947
|
+
} catch (error) {
|
|
2948
|
+
try {
|
|
2949
|
+
await transaction.rollback();
|
|
2950
|
+
} catch {
|
|
2951
|
+
}
|
|
2952
|
+
throw new MastraError(
|
|
2953
|
+
{
|
|
2954
|
+
id: createStorageErrorId("MSSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
2955
|
+
domain: ErrorDomain.STORAGE,
|
|
2956
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2957
|
+
details: {
|
|
2958
|
+
runId,
|
|
2959
|
+
workflowName
|
|
2960
|
+
}
|
|
2961
|
+
},
|
|
2962
|
+
error
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
async listWorkflowRuns({
|
|
2003
2967
|
workflowName,
|
|
2004
2968
|
fromDate,
|
|
2005
2969
|
toDate,
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
resourceId
|
|
2970
|
+
page,
|
|
2971
|
+
perPage,
|
|
2972
|
+
resourceId,
|
|
2973
|
+
status
|
|
2009
2974
|
} = {}) {
|
|
2010
2975
|
try {
|
|
2011
2976
|
const conditions = [];
|
|
@@ -2014,13 +2979,17 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2014
2979
|
conditions.push(`[workflow_name] = @workflowName`);
|
|
2015
2980
|
paramMap["workflowName"] = workflowName;
|
|
2016
2981
|
}
|
|
2982
|
+
if (status) {
|
|
2983
|
+
conditions.push(`JSON_VALUE([snapshot], '$.status') = @status`);
|
|
2984
|
+
paramMap["status"] = status;
|
|
2985
|
+
}
|
|
2017
2986
|
if (resourceId) {
|
|
2018
2987
|
const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
|
|
2019
2988
|
if (hasResourceId) {
|
|
2020
2989
|
conditions.push(`[resourceId] = @resourceId`);
|
|
2021
2990
|
paramMap["resourceId"] = resourceId;
|
|
2022
2991
|
} else {
|
|
2023
|
-
|
|
2992
|
+
this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
2024
2993
|
}
|
|
2025
2994
|
}
|
|
2026
2995
|
if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
|
|
@@ -2037,29 +3006,32 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2037
3006
|
const request = this.pool.request();
|
|
2038
3007
|
Object.entries(paramMap).forEach(([key, value]) => {
|
|
2039
3008
|
if (value instanceof Date) {
|
|
2040
|
-
request.input(key,
|
|
3009
|
+
request.input(key, sql3.DateTime, value);
|
|
2041
3010
|
} else {
|
|
2042
3011
|
request.input(key, value);
|
|
2043
3012
|
}
|
|
2044
3013
|
});
|
|
2045
|
-
|
|
3014
|
+
const usePagination = typeof perPage === "number" && typeof page === "number";
|
|
3015
|
+
if (usePagination) {
|
|
2046
3016
|
const countQuery = `SELECT COUNT(*) as count FROM ${tableName} ${whereClause}`;
|
|
2047
3017
|
const countResult = await request.query(countQuery);
|
|
2048
3018
|
total = Number(countResult.recordset[0]?.count || 0);
|
|
2049
3019
|
}
|
|
2050
3020
|
let query = `SELECT * FROM ${tableName} ${whereClause} ORDER BY [seq_id] DESC`;
|
|
2051
|
-
if (
|
|
2052
|
-
|
|
2053
|
-
|
|
3021
|
+
if (usePagination) {
|
|
3022
|
+
const normalizedPerPage = normalizePerPage(perPage, Number.MAX_SAFE_INTEGER);
|
|
3023
|
+
const offset = page * normalizedPerPage;
|
|
3024
|
+
query += ` OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
3025
|
+
request.input("perPage", normalizedPerPage);
|
|
2054
3026
|
request.input("offset", offset);
|
|
2055
3027
|
}
|
|
2056
3028
|
const result = await request.query(query);
|
|
2057
|
-
const runs = (result.recordset || []).map((row) => parseWorkflowRun(row));
|
|
3029
|
+
const runs = (result.recordset || []).map((row) => this.parseWorkflowRun(row));
|
|
2058
3030
|
return { runs, total: total || runs.length };
|
|
2059
3031
|
} catch (error) {
|
|
2060
3032
|
throw new MastraError(
|
|
2061
3033
|
{
|
|
2062
|
-
id: "
|
|
3034
|
+
id: createStorageErrorId("MSSQL", "LIST_WORKFLOW_RUNS", "FAILED"),
|
|
2063
3035
|
domain: ErrorDomain.STORAGE,
|
|
2064
3036
|
category: ErrorCategory.THIRD_PARTY,
|
|
2065
3037
|
details: {
|
|
@@ -2079,7 +3051,10 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2079
3051
|
isConnected = null;
|
|
2080
3052
|
stores;
|
|
2081
3053
|
constructor(config) {
|
|
2082
|
-
|
|
3054
|
+
if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
|
|
3055
|
+
throw new Error("MSSQLStore: id must be provided and cannot be empty.");
|
|
3056
|
+
}
|
|
3057
|
+
super({ id: config.id, name: "MSSQLStore", disableInit: config.disableInit });
|
|
2083
3058
|
try {
|
|
2084
3059
|
if ("connectionString" in config) {
|
|
2085
3060
|
if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
|
|
@@ -2094,7 +3069,7 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2094
3069
|
}
|
|
2095
3070
|
}
|
|
2096
3071
|
this.schema = config.schemaName || "dbo";
|
|
2097
|
-
this.pool = "connectionString" in config ? new
|
|
3072
|
+
this.pool = "connectionString" in config ? new sql3.ConnectionPool(config.connectionString) : new sql3.ConnectionPool({
|
|
2098
3073
|
server: config.server,
|
|
2099
3074
|
database: config.database,
|
|
2100
3075
|
user: config.user,
|
|
@@ -2102,24 +3077,22 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2102
3077
|
port: config.port,
|
|
2103
3078
|
options: config.options || { encrypt: true, trustServerCertificate: true }
|
|
2104
3079
|
});
|
|
2105
|
-
const legacyEvals = new LegacyEvalsMSSQL({ pool: this.pool, schema: this.schema });
|
|
2106
3080
|
const operations = new StoreOperationsMSSQL({ pool: this.pool, schemaName: this.schema });
|
|
2107
3081
|
const scores = new ScoresMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2108
|
-
const traces = new TracesMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2109
3082
|
const workflows = new WorkflowsMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2110
3083
|
const memory = new MemoryMSSQL({ pool: this.pool, schema: this.schema, operations });
|
|
3084
|
+
const observability = new ObservabilityMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
2111
3085
|
this.stores = {
|
|
2112
3086
|
operations,
|
|
2113
3087
|
scores,
|
|
2114
|
-
traces,
|
|
2115
3088
|
workflows,
|
|
2116
|
-
|
|
2117
|
-
|
|
3089
|
+
memory,
|
|
3090
|
+
observability
|
|
2118
3091
|
};
|
|
2119
3092
|
} catch (e) {
|
|
2120
3093
|
throw new MastraError(
|
|
2121
3094
|
{
|
|
2122
|
-
id: "
|
|
3095
|
+
id: createStorageErrorId("MSSQL", "INITIALIZATION", "FAILED"),
|
|
2123
3096
|
domain: ErrorDomain.STORAGE,
|
|
2124
3097
|
category: ErrorCategory.USER
|
|
2125
3098
|
},
|
|
@@ -2134,11 +3107,16 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2134
3107
|
try {
|
|
2135
3108
|
await this.isConnected;
|
|
2136
3109
|
await super.init();
|
|
3110
|
+
try {
|
|
3111
|
+
await this.stores.operations.createAutomaticIndexes();
|
|
3112
|
+
} catch (indexError) {
|
|
3113
|
+
this.logger?.warn?.("Failed to create indexes:", indexError);
|
|
3114
|
+
}
|
|
2137
3115
|
} catch (error) {
|
|
2138
3116
|
this.isConnected = null;
|
|
2139
3117
|
throw new MastraError(
|
|
2140
3118
|
{
|
|
2141
|
-
id: "
|
|
3119
|
+
id: createStorageErrorId("MSSQL", "INIT", "FAILED"),
|
|
2142
3120
|
domain: ErrorDomain.STORAGE,
|
|
2143
3121
|
category: ErrorCategory.THIRD_PARTY
|
|
2144
3122
|
},
|
|
@@ -2160,28 +3138,12 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2160
3138
|
resourceWorkingMemory: true,
|
|
2161
3139
|
hasColumn: true,
|
|
2162
3140
|
createTable: true,
|
|
2163
|
-
deleteMessages: true
|
|
3141
|
+
deleteMessages: true,
|
|
3142
|
+
listScoresBySpan: true,
|
|
3143
|
+
observabilityInstance: true,
|
|
3144
|
+
indexManagement: true
|
|
2164
3145
|
};
|
|
2165
3146
|
}
|
|
2166
|
-
/** @deprecated use getEvals instead */
|
|
2167
|
-
async getEvalsByAgentName(agentName, type) {
|
|
2168
|
-
return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
|
|
2169
|
-
}
|
|
2170
|
-
async getEvals(options = {}) {
|
|
2171
|
-
return this.stores.legacyEvals.getEvals(options);
|
|
2172
|
-
}
|
|
2173
|
-
/**
|
|
2174
|
-
* @deprecated use getTracesPaginated instead
|
|
2175
|
-
*/
|
|
2176
|
-
async getTraces(args) {
|
|
2177
|
-
return this.stores.traces.getTraces(args);
|
|
2178
|
-
}
|
|
2179
|
-
async getTracesPaginated(args) {
|
|
2180
|
-
return this.stores.traces.getTracesPaginated(args);
|
|
2181
|
-
}
|
|
2182
|
-
async batchTraceInsert({ records }) {
|
|
2183
|
-
return this.stores.traces.batchTraceInsert({ records });
|
|
2184
|
-
}
|
|
2185
3147
|
async createTable({
|
|
2186
3148
|
tableName,
|
|
2187
3149
|
schema
|
|
@@ -2216,15 +3178,6 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2216
3178
|
async getThreadById({ threadId }) {
|
|
2217
3179
|
return this.stores.memory.getThreadById({ threadId });
|
|
2218
3180
|
}
|
|
2219
|
-
/**
|
|
2220
|
-
* @deprecated use getThreadsByResourceIdPaginated instead
|
|
2221
|
-
*/
|
|
2222
|
-
async getThreadsByResourceId(args) {
|
|
2223
|
-
return this.stores.memory.getThreadsByResourceId(args);
|
|
2224
|
-
}
|
|
2225
|
-
async getThreadsByResourceIdPaginated(args) {
|
|
2226
|
-
return this.stores.memory.getThreadsByResourceIdPaginated(args);
|
|
2227
|
-
}
|
|
2228
3181
|
async saveThread({ thread }) {
|
|
2229
3182
|
return this.stores.memory.saveThread({ thread });
|
|
2230
3183
|
}
|
|
@@ -2238,17 +3191,8 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2238
3191
|
async deleteThread({ threadId }) {
|
|
2239
3192
|
return this.stores.memory.deleteThread({ threadId });
|
|
2240
3193
|
}
|
|
2241
|
-
async
|
|
2242
|
-
return this.stores.memory.
|
|
2243
|
-
}
|
|
2244
|
-
async getMessagesById({
|
|
2245
|
-
messageIds,
|
|
2246
|
-
format
|
|
2247
|
-
}) {
|
|
2248
|
-
return this.stores.memory.getMessagesById({ messageIds, format });
|
|
2249
|
-
}
|
|
2250
|
-
async getMessagesPaginated(args) {
|
|
2251
|
-
return this.stores.memory.getMessagesPaginated(args);
|
|
3194
|
+
async listMessagesById({ messageIds }) {
|
|
3195
|
+
return this.stores.memory.listMessagesById({ messageIds });
|
|
2252
3196
|
}
|
|
2253
3197
|
async saveMessages(args) {
|
|
2254
3198
|
return this.stores.memory.saveMessages(args);
|
|
@@ -2282,9 +3226,9 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2282
3226
|
runId,
|
|
2283
3227
|
stepId,
|
|
2284
3228
|
result,
|
|
2285
|
-
|
|
3229
|
+
requestContext
|
|
2286
3230
|
}) {
|
|
2287
|
-
return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result,
|
|
3231
|
+
return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
|
|
2288
3232
|
}
|
|
2289
3233
|
async updateWorkflowState({
|
|
2290
3234
|
workflowName,
|
|
@@ -2307,15 +3251,8 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2307
3251
|
}) {
|
|
2308
3252
|
return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
|
|
2309
3253
|
}
|
|
2310
|
-
async
|
|
2311
|
-
|
|
2312
|
-
fromDate,
|
|
2313
|
-
toDate,
|
|
2314
|
-
limit,
|
|
2315
|
-
offset,
|
|
2316
|
-
resourceId
|
|
2317
|
-
} = {}) {
|
|
2318
|
-
return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
|
|
3254
|
+
async listWorkflowRuns(args = {}) {
|
|
3255
|
+
return this.stores.workflows.listWorkflowRuns(args);
|
|
2319
3256
|
}
|
|
2320
3257
|
async getWorkflowRunById({
|
|
2321
3258
|
runId,
|
|
@@ -2323,41 +3260,114 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
2323
3260
|
}) {
|
|
2324
3261
|
return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
|
|
2325
3262
|
}
|
|
3263
|
+
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
3264
|
+
return this.stores.workflows.deleteWorkflowRunById({ runId, workflowName });
|
|
3265
|
+
}
|
|
2326
3266
|
async close() {
|
|
2327
3267
|
await this.pool.close();
|
|
2328
3268
|
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Index Management
|
|
3271
|
+
*/
|
|
3272
|
+
async createIndex(options) {
|
|
3273
|
+
return this.stores.operations.createIndex(options);
|
|
3274
|
+
}
|
|
3275
|
+
async listIndexes(tableName) {
|
|
3276
|
+
return this.stores.operations.listIndexes(tableName);
|
|
3277
|
+
}
|
|
3278
|
+
async describeIndex(indexName) {
|
|
3279
|
+
return this.stores.operations.describeIndex(indexName);
|
|
3280
|
+
}
|
|
3281
|
+
async dropIndex(indexName) {
|
|
3282
|
+
return this.stores.operations.dropIndex(indexName);
|
|
3283
|
+
}
|
|
3284
|
+
/**
|
|
3285
|
+
* Tracing / Observability
|
|
3286
|
+
*/
|
|
3287
|
+
getObservabilityStore() {
|
|
3288
|
+
if (!this.stores.observability) {
|
|
3289
|
+
throw new MastraError({
|
|
3290
|
+
id: createStorageErrorId("MSSQL", "OBSERVABILITY", "NOT_INITIALIZED"),
|
|
3291
|
+
domain: ErrorDomain.STORAGE,
|
|
3292
|
+
category: ErrorCategory.SYSTEM,
|
|
3293
|
+
text: "Observability storage is not initialized"
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
3296
|
+
return this.stores.observability;
|
|
3297
|
+
}
|
|
3298
|
+
async createSpan(span) {
|
|
3299
|
+
return this.getObservabilityStore().createSpan(span);
|
|
3300
|
+
}
|
|
3301
|
+
async updateSpan({
|
|
3302
|
+
spanId,
|
|
3303
|
+
traceId,
|
|
3304
|
+
updates
|
|
3305
|
+
}) {
|
|
3306
|
+
return this.getObservabilityStore().updateSpan({ spanId, traceId, updates });
|
|
3307
|
+
}
|
|
3308
|
+
async getTrace(traceId) {
|
|
3309
|
+
return this.getObservabilityStore().getTrace(traceId);
|
|
3310
|
+
}
|
|
3311
|
+
async getTracesPaginated(args) {
|
|
3312
|
+
return this.getObservabilityStore().getTracesPaginated(args);
|
|
3313
|
+
}
|
|
3314
|
+
async batchCreateSpans(args) {
|
|
3315
|
+
return this.getObservabilityStore().batchCreateSpans(args);
|
|
3316
|
+
}
|
|
3317
|
+
async batchUpdateSpans(args) {
|
|
3318
|
+
return this.getObservabilityStore().batchUpdateSpans(args);
|
|
3319
|
+
}
|
|
3320
|
+
async batchDeleteTraces(args) {
|
|
3321
|
+
return this.getObservabilityStore().batchDeleteTraces(args);
|
|
3322
|
+
}
|
|
2329
3323
|
/**
|
|
2330
3324
|
* Scorers
|
|
2331
3325
|
*/
|
|
2332
3326
|
async getScoreById({ id: _id }) {
|
|
2333
3327
|
return this.stores.scores.getScoreById({ id: _id });
|
|
2334
3328
|
}
|
|
2335
|
-
async
|
|
3329
|
+
async listScoresByScorerId({
|
|
2336
3330
|
scorerId: _scorerId,
|
|
2337
|
-
pagination: _pagination
|
|
3331
|
+
pagination: _pagination,
|
|
3332
|
+
entityId: _entityId,
|
|
3333
|
+
entityType: _entityType,
|
|
3334
|
+
source: _source
|
|
2338
3335
|
}) {
|
|
2339
|
-
return this.stores.scores.
|
|
3336
|
+
return this.stores.scores.listScoresByScorerId({
|
|
3337
|
+
scorerId: _scorerId,
|
|
3338
|
+
pagination: _pagination,
|
|
3339
|
+
entityId: _entityId,
|
|
3340
|
+
entityType: _entityType,
|
|
3341
|
+
source: _source
|
|
3342
|
+
});
|
|
2340
3343
|
}
|
|
2341
|
-
async saveScore(
|
|
2342
|
-
return this.stores.scores.saveScore(
|
|
3344
|
+
async saveScore(score) {
|
|
3345
|
+
return this.stores.scores.saveScore(score);
|
|
2343
3346
|
}
|
|
2344
|
-
async
|
|
3347
|
+
async listScoresByRunId({
|
|
2345
3348
|
runId: _runId,
|
|
2346
3349
|
pagination: _pagination
|
|
2347
3350
|
}) {
|
|
2348
|
-
return this.stores.scores.
|
|
3351
|
+
return this.stores.scores.listScoresByRunId({ runId: _runId, pagination: _pagination });
|
|
2349
3352
|
}
|
|
2350
|
-
async
|
|
3353
|
+
async listScoresByEntityId({
|
|
2351
3354
|
entityId: _entityId,
|
|
2352
3355
|
entityType: _entityType,
|
|
2353
3356
|
pagination: _pagination
|
|
2354
3357
|
}) {
|
|
2355
|
-
return this.stores.scores.
|
|
3358
|
+
return this.stores.scores.listScoresByEntityId({
|
|
2356
3359
|
entityId: _entityId,
|
|
2357
3360
|
entityType: _entityType,
|
|
2358
3361
|
pagination: _pagination
|
|
2359
3362
|
});
|
|
2360
3363
|
}
|
|
3364
|
+
async listScoresBySpan({
|
|
3365
|
+
traceId,
|
|
3366
|
+
spanId,
|
|
3367
|
+
pagination: _pagination
|
|
3368
|
+
}) {
|
|
3369
|
+
return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination: _pagination });
|
|
3370
|
+
}
|
|
2361
3371
|
};
|
|
2362
3372
|
|
|
2363
3373
|
export { MSSQLStore };
|