@mastra/mssql 0.0.0-scorers-api-v2-20250801171841 → 0.0.0-scorers-logs-20251208093427
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 +1080 -4
- package/README.md +324 -37
- package/dist/index.cjs +2960 -1434
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2940 -1414
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +69 -0
- package/dist/storage/domains/memory/index.d.ts.map +1 -0
- 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 +114 -0
- package/dist/storage/domains/operations/index.d.ts.map +1 -0
- package/dist/storage/domains/scores/index.d.ts +55 -0
- package/dist/storage/domains/scores/index.d.ts.map +1 -0
- package/dist/storage/domains/utils.d.ts +25 -0
- package/dist/storage/domains/utils.d.ts.map +1 -0
- package/dist/storage/domains/workflows/index.d.ts +50 -0
- package/dist/storage/domains/workflows/index.d.ts.map +1 -0
- package/dist/storage/index.d.ts +147 -121
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +31 -12
- package/docker-compose.yaml +0 -14
- package/eslint.config.js +0 -6
- package/src/index.ts +0 -2
- package/src/storage/index.test.ts +0 -2228
- package/src/storage/index.ts +0 -2136
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -22
- package/vitest.config.ts +0 -12
package/dist/index.js
CHANGED
|
@@ -1,1215 +1,2822 @@
|
|
|
1
|
-
import { MessageList } from '@mastra/core/agent';
|
|
2
1
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
3
|
-
import { MastraStorage,
|
|
4
|
-
import
|
|
5
|
-
import
|
|
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 sql2 from 'mssql';
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
function getSchemaName(schema) {
|
|
11
|
+
return schema ? `[${parseSqlIdentifier(schema, "schema name")}]` : void 0;
|
|
12
|
+
}
|
|
13
|
+
function getTableName({ indexName, schemaName }) {
|
|
14
|
+
const parsedIndexName = parseSqlIdentifier(indexName, "index name");
|
|
15
|
+
const quotedIndexName = `[${parsedIndexName}]`;
|
|
16
|
+
const quotedSchemaName = schemaName;
|
|
17
|
+
return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
|
|
18
|
+
}
|
|
19
|
+
function buildDateRangeFilter(dateRange, fieldName) {
|
|
20
|
+
const filters = {};
|
|
21
|
+
if (dateRange?.start) {
|
|
22
|
+
filters[`${fieldName}_gte`] = dateRange.start;
|
|
23
|
+
}
|
|
24
|
+
if (dateRange?.end) {
|
|
25
|
+
filters[`${fieldName}_lte`] = dateRange.end;
|
|
26
|
+
}
|
|
27
|
+
return filters;
|
|
28
|
+
}
|
|
29
|
+
function 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;
|
|
20
64
|
}
|
|
65
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] IN (${inParamNames.join(", ")})`);
|
|
66
|
+
}
|
|
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];
|
|
21
74
|
} else {
|
|
22
|
-
const
|
|
23
|
-
for (const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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;
|
|
27
80
|
}
|
|
81
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] IN (${inParamNames.join(", ")})`);
|
|
28
82
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
user: config.user,
|
|
34
|
-
password: config.password,
|
|
35
|
-
port: config.port,
|
|
36
|
-
options: config.options || { encrypt: true, trustServerCertificate: true }
|
|
37
|
-
});
|
|
38
|
-
} catch (e) {
|
|
39
|
-
throw new MastraError(
|
|
40
|
-
{
|
|
41
|
-
id: "MASTRA_STORAGE_MSSQL_STORE_INITIALIZATION_FAILED",
|
|
42
|
-
domain: ErrorDomain.STORAGE,
|
|
43
|
-
category: ErrorCategory.USER
|
|
44
|
-
},
|
|
45
|
-
e
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
async init() {
|
|
50
|
-
if (this.isConnected === null) {
|
|
51
|
-
this.isConnected = this._performInitializationAndStore();
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
54
|
-
await this.isConnected;
|
|
55
|
-
await super.init();
|
|
56
|
-
} catch (error) {
|
|
57
|
-
this.isConnected = null;
|
|
58
|
-
throw new MastraError(
|
|
59
|
-
{
|
|
60
|
-
id: "MASTRA_STORAGE_MSSQL_STORE_INIT_FAILED",
|
|
61
|
-
domain: ErrorDomain.STORAGE,
|
|
62
|
-
category: ErrorCategory.THIRD_PARTY
|
|
63
|
-
},
|
|
64
|
-
error
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
async _performInitializationAndStore() {
|
|
69
|
-
try {
|
|
70
|
-
await this.pool.connect();
|
|
71
|
-
return true;
|
|
72
|
-
} catch (err) {
|
|
73
|
-
throw err;
|
|
83
|
+
} else {
|
|
84
|
+
const paramName = `p${paramIndex++}`;
|
|
85
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] = @${paramName}`);
|
|
86
|
+
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
74
87
|
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
getSchemaName() {
|
|
92
|
-
return this.schema ? `[${parseSqlIdentifier(this.schema, "schema name")}]` : void 0;
|
|
93
|
-
}
|
|
94
|
-
transformEvalRow(row) {
|
|
95
|
-
let testInfoValue = null, resultValue = null;
|
|
96
|
-
if (row.test_info) {
|
|
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") {
|
|
97
103
|
try {
|
|
98
|
-
|
|
104
|
+
result[key] = JSON.parse(value);
|
|
99
105
|
} catch {
|
|
106
|
+
result[key] = value;
|
|
100
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;
|
|
101
116
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
117
|
+
});
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/storage/domains/memory/index.ts
|
|
122
|
+
var MemoryMSSQL = class extends MemoryStorage {
|
|
123
|
+
pool;
|
|
124
|
+
schema;
|
|
125
|
+
operations;
|
|
126
|
+
_parseAndFormatMessages(messages, format) {
|
|
127
|
+
const messagesWithParsedContent = messages.map((message) => {
|
|
128
|
+
if (typeof message.content === "string") {
|
|
129
|
+
try {
|
|
130
|
+
return { ...message, content: JSON.parse(message.content) };
|
|
131
|
+
} catch {
|
|
132
|
+
return message;
|
|
133
|
+
}
|
|
106
134
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
result: resultValue,
|
|
113
|
-
metricName: row.metric_name,
|
|
114
|
-
instructions: row.instructions,
|
|
115
|
-
testInfo: testInfoValue,
|
|
116
|
-
globalRunId: row.global_run_id,
|
|
117
|
-
runId: row.run_id,
|
|
118
|
-
createdAt: row.created_at
|
|
119
|
-
};
|
|
135
|
+
return message;
|
|
136
|
+
});
|
|
137
|
+
const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
|
|
138
|
+
const list = new MessageList().add(cleanMessages, "memory");
|
|
139
|
+
return format === "v2" ? list.get.all.db() : list.get.all.v1();
|
|
120
140
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
query += " ORDER BY created_at DESC";
|
|
131
|
-
const request = this.pool.request();
|
|
132
|
-
request.input("p1", agentName);
|
|
133
|
-
const result = await request.query(query);
|
|
134
|
-
const rows = result.recordset;
|
|
135
|
-
return typeof this.transformEvalRow === "function" ? rows?.map((row) => this.transformEvalRow(row)) ?? [] : rows ?? [];
|
|
136
|
-
} catch (error) {
|
|
137
|
-
if (error && error.number === 208 && error.message && error.message.includes("Invalid object name")) {
|
|
138
|
-
return [];
|
|
139
|
-
}
|
|
140
|
-
console.error("Failed to get evals for the specified agent: " + error?.message);
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
141
|
+
constructor({
|
|
142
|
+
pool,
|
|
143
|
+
schema,
|
|
144
|
+
operations
|
|
145
|
+
}) {
|
|
146
|
+
super();
|
|
147
|
+
this.pool = pool;
|
|
148
|
+
this.schema = schema;
|
|
149
|
+
this.operations = operations;
|
|
143
150
|
}
|
|
144
|
-
async
|
|
145
|
-
const transaction = this.pool.transaction();
|
|
151
|
+
async getThreadById({ threadId }) {
|
|
146
152
|
try {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
const sql5 = `SELECT
|
|
154
|
+
id,
|
|
155
|
+
[resourceId],
|
|
156
|
+
title,
|
|
157
|
+
metadata,
|
|
158
|
+
[createdAt],
|
|
159
|
+
[updatedAt]
|
|
160
|
+
FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })}
|
|
161
|
+
WHERE id = @threadId`;
|
|
162
|
+
const request = this.pool.request();
|
|
163
|
+
request.input("threadId", threadId);
|
|
164
|
+
const resultSet = await request.query(sql5);
|
|
165
|
+
const thread = resultSet.recordset[0] || null;
|
|
166
|
+
if (!thread) {
|
|
167
|
+
return null;
|
|
150
168
|
}
|
|
151
|
-
|
|
169
|
+
return {
|
|
170
|
+
...thread,
|
|
171
|
+
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
172
|
+
createdAt: thread.createdAt,
|
|
173
|
+
updatedAt: thread.updatedAt
|
|
174
|
+
};
|
|
152
175
|
} catch (error) {
|
|
153
|
-
await transaction.rollback();
|
|
154
176
|
throw new MastraError(
|
|
155
177
|
{
|
|
156
|
-
id: "
|
|
178
|
+
id: createStorageErrorId("MSSQL", "GET_THREAD_BY_ID", "FAILED"),
|
|
157
179
|
domain: ErrorDomain.STORAGE,
|
|
158
180
|
category: ErrorCategory.THIRD_PARTY,
|
|
159
181
|
details: {
|
|
160
|
-
|
|
161
|
-
numberOfRecords: records.length
|
|
182
|
+
threadId
|
|
162
183
|
}
|
|
163
184
|
},
|
|
164
185
|
error
|
|
165
186
|
);
|
|
166
187
|
}
|
|
167
188
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const { name, scope, page = 0, perPage: perPageInput, attributes, filters, dateRange } = args;
|
|
181
|
-
const fromDate = dateRange?.start;
|
|
182
|
-
const toDate = dateRange?.end;
|
|
183
|
-
const perPage = perPageInput !== void 0 ? perPageInput : 100;
|
|
184
|
-
const currentOffset = page * perPage;
|
|
185
|
-
const paramMap = {};
|
|
186
|
-
const conditions = [];
|
|
187
|
-
let paramIndex = 1;
|
|
188
|
-
if (name) {
|
|
189
|
-
const paramName = `p${paramIndex++}`;
|
|
190
|
-
conditions.push(`[name] LIKE @${paramName}`);
|
|
191
|
-
paramMap[paramName] = `${name}%`;
|
|
192
|
-
}
|
|
193
|
-
if (scope) {
|
|
194
|
-
const paramName = `p${paramIndex++}`;
|
|
195
|
-
conditions.push(`[scope] = @${paramName}`);
|
|
196
|
-
paramMap[paramName] = scope;
|
|
197
|
-
}
|
|
198
|
-
if (attributes) {
|
|
199
|
-
Object.entries(attributes).forEach(([key, value]) => {
|
|
200
|
-
const parsedKey = parseFieldKey(key);
|
|
201
|
-
const paramName = `p${paramIndex++}`;
|
|
202
|
-
conditions.push(`JSON_VALUE([attributes], '$.${parsedKey}') = @${paramName}`);
|
|
203
|
-
paramMap[paramName] = value;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
if (filters) {
|
|
207
|
-
Object.entries(filters).forEach(([key, value]) => {
|
|
208
|
-
const parsedKey = parseFieldKey(key);
|
|
209
|
-
const paramName = `p${paramIndex++}`;
|
|
210
|
-
conditions.push(`[${parsedKey}] = @${paramName}`);
|
|
211
|
-
paramMap[paramName] = value;
|
|
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
|
+
}
|
|
212
201
|
});
|
|
213
202
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
paramMap[paramName] = fromDate.toISOString();
|
|
218
|
-
}
|
|
219
|
-
if (toDate instanceof Date && !isNaN(toDate.getTime())) {
|
|
220
|
-
const paramName = `p${paramIndex++}`;
|
|
221
|
-
conditions.push(`[createdAt] <= @${paramName}`);
|
|
222
|
-
paramMap[paramName] = toDate.toISOString();
|
|
223
|
-
}
|
|
224
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
225
|
-
const countQuery = `SELECT COUNT(*) as total FROM ${this.getTableName(TABLE_TRACES)} ${whereClause}`;
|
|
226
|
-
let total = 0;
|
|
203
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
204
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
205
|
+
const { field, direction } = this.parseOrderBy(orderBy);
|
|
227
206
|
try {
|
|
207
|
+
const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
|
|
208
|
+
const countQuery = `SELECT COUNT(*) as count ${baseQuery}`;
|
|
228
209
|
const countRequest = this.pool.request();
|
|
229
|
-
|
|
230
|
-
if (value instanceof Date) {
|
|
231
|
-
countRequest.input(key, sql.DateTime, value);
|
|
232
|
-
} else {
|
|
233
|
-
countRequest.input(key, value);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
210
|
+
countRequest.input("resourceId", resourceId);
|
|
236
211
|
const countResult = await countRequest.query(countQuery);
|
|
237
|
-
total = parseInt(countResult.recordset[0]
|
|
212
|
+
const total = parseInt(countResult.recordset[0]?.count ?? "0", 10);
|
|
213
|
+
if (total === 0) {
|
|
214
|
+
return {
|
|
215
|
+
threads: [],
|
|
216
|
+
total: 0,
|
|
217
|
+
page,
|
|
218
|
+
perPage: perPageForResponse,
|
|
219
|
+
hasMore: false
|
|
220
|
+
};
|
|
221
|
+
}
|
|
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`;
|
|
226
|
+
const dataRequest = this.pool.request();
|
|
227
|
+
dataRequest.input("resourceId", resourceId);
|
|
228
|
+
dataRequest.input("offset", offset);
|
|
229
|
+
if (limitValue > 2147483647) {
|
|
230
|
+
dataRequest.input("perPage", sql2.BigInt, limitValue);
|
|
231
|
+
} else {
|
|
232
|
+
dataRequest.input("perPage", limitValue);
|
|
233
|
+
}
|
|
234
|
+
const rowsResult = await dataRequest.query(dataQuery);
|
|
235
|
+
const rows = rowsResult.recordset || [];
|
|
236
|
+
const threads = rows.map((thread) => ({
|
|
237
|
+
...thread,
|
|
238
|
+
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
239
|
+
createdAt: thread.createdAt,
|
|
240
|
+
updatedAt: thread.updatedAt
|
|
241
|
+
}));
|
|
242
|
+
return {
|
|
243
|
+
threads,
|
|
244
|
+
total,
|
|
245
|
+
page,
|
|
246
|
+
perPage: perPageForResponse,
|
|
247
|
+
hasMore: perPageInput === false ? false : offset + perPage < total
|
|
248
|
+
};
|
|
238
249
|
} catch (error) {
|
|
239
|
-
|
|
250
|
+
const mastraError = new MastraError(
|
|
240
251
|
{
|
|
241
|
-
id: "
|
|
252
|
+
id: createStorageErrorId("MSSQL", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
|
|
242
253
|
domain: ErrorDomain.STORAGE,
|
|
243
254
|
category: ErrorCategory.THIRD_PARTY,
|
|
244
255
|
details: {
|
|
245
|
-
|
|
246
|
-
|
|
256
|
+
resourceId,
|
|
257
|
+
page
|
|
247
258
|
}
|
|
248
259
|
},
|
|
249
260
|
error
|
|
250
261
|
);
|
|
251
|
-
|
|
252
|
-
|
|
262
|
+
this.logger?.error?.(mastraError.toString());
|
|
263
|
+
this.logger?.trackException?.(mastraError);
|
|
253
264
|
return {
|
|
254
|
-
|
|
265
|
+
threads: [],
|
|
255
266
|
total: 0,
|
|
256
267
|
page,
|
|
257
|
-
perPage,
|
|
268
|
+
perPage: perPageForResponse,
|
|
258
269
|
hasMore: false
|
|
259
270
|
};
|
|
260
271
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
272
|
+
}
|
|
273
|
+
async saveThread({ thread }) {
|
|
274
|
+
try {
|
|
275
|
+
const table = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
|
|
276
|
+
const mergeSql = `MERGE INTO ${table} WITH (HOLDLOCK) AS target
|
|
277
|
+
USING (SELECT @id AS id) AS source
|
|
278
|
+
ON (target.id = source.id)
|
|
279
|
+
WHEN MATCHED THEN
|
|
280
|
+
UPDATE SET
|
|
281
|
+
[resourceId] = @resourceId,
|
|
282
|
+
title = @title,
|
|
283
|
+
metadata = @metadata,
|
|
284
|
+
[updatedAt] = @updatedAt
|
|
285
|
+
WHEN NOT MATCHED THEN
|
|
286
|
+
INSERT (id, [resourceId], title, metadata, [createdAt], [updatedAt])
|
|
287
|
+
VALUES (@id, @resourceId, @title, @metadata, @createdAt, @updatedAt);`;
|
|
288
|
+
const req = this.pool.request();
|
|
289
|
+
req.input("id", thread.id);
|
|
290
|
+
req.input("resourceId", thread.resourceId);
|
|
291
|
+
req.input("title", thread.title);
|
|
292
|
+
const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
|
|
293
|
+
if (metadata === null) {
|
|
294
|
+
req.input("metadata", sql2.NVarChar, null);
|
|
266
295
|
} else {
|
|
267
|
-
|
|
296
|
+
req.input("metadata", metadata);
|
|
268
297
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const rowsResult = await dataRequest.query(dataQuery);
|
|
274
|
-
const rows = rowsResult.recordset;
|
|
275
|
-
const traces = rows.map((row) => ({
|
|
276
|
-
id: row.id,
|
|
277
|
-
parentSpanId: row.parentSpanId,
|
|
278
|
-
traceId: row.traceId,
|
|
279
|
-
name: row.name,
|
|
280
|
-
scope: row.scope,
|
|
281
|
-
kind: row.kind,
|
|
282
|
-
status: JSON.parse(row.status),
|
|
283
|
-
events: JSON.parse(row.events),
|
|
284
|
-
links: JSON.parse(row.links),
|
|
285
|
-
attributes: JSON.parse(row.attributes),
|
|
286
|
-
startTime: row.startTime,
|
|
287
|
-
endTime: row.endTime,
|
|
288
|
-
other: row.other,
|
|
289
|
-
createdAt: row.createdAt
|
|
290
|
-
}));
|
|
291
|
-
return {
|
|
292
|
-
traces,
|
|
293
|
-
total,
|
|
294
|
-
page,
|
|
295
|
-
perPage,
|
|
296
|
-
hasMore: currentOffset + traces.length < total
|
|
297
|
-
};
|
|
298
|
+
req.input("createdAt", sql2.DateTime2, thread.createdAt);
|
|
299
|
+
req.input("updatedAt", sql2.DateTime2, thread.updatedAt);
|
|
300
|
+
await req.query(mergeSql);
|
|
301
|
+
return thread;
|
|
298
302
|
} catch (error) {
|
|
299
303
|
throw new MastraError(
|
|
300
304
|
{
|
|
301
|
-
id: "
|
|
305
|
+
id: createStorageErrorId("MSSQL", "SAVE_THREAD", "FAILED"),
|
|
302
306
|
domain: ErrorDomain.STORAGE,
|
|
303
307
|
category: ErrorCategory.THIRD_PARTY,
|
|
304
308
|
details: {
|
|
305
|
-
|
|
306
|
-
scope: args.scope ?? ""
|
|
309
|
+
threadId: thread.id
|
|
307
310
|
}
|
|
308
311
|
},
|
|
309
312
|
error
|
|
310
313
|
);
|
|
311
314
|
}
|
|
312
315
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Updates a thread's title and metadata, merging with existing metadata. Returns the updated thread.
|
|
318
|
+
*/
|
|
319
|
+
async updateThread({
|
|
320
|
+
id,
|
|
321
|
+
title,
|
|
322
|
+
metadata
|
|
323
|
+
}) {
|
|
324
|
+
const existingThread = await this.getThreadById({ threadId: id });
|
|
325
|
+
if (!existingThread) {
|
|
326
|
+
throw new MastraError({
|
|
327
|
+
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
328
|
+
domain: ErrorDomain.STORAGE,
|
|
329
|
+
category: ErrorCategory.USER,
|
|
330
|
+
text: `Thread ${id} not found`,
|
|
331
|
+
details: {
|
|
332
|
+
threadId: id,
|
|
333
|
+
title
|
|
334
|
+
}
|
|
335
|
+
});
|
|
316
336
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
this.setupSchemaPromise = null;
|
|
342
|
-
throw error;
|
|
343
|
-
} finally {
|
|
344
|
-
this.setupSchemaPromise = null;
|
|
345
|
-
}
|
|
346
|
-
})();
|
|
347
|
-
}
|
|
348
|
-
await this.setupSchemaPromise;
|
|
349
|
-
}
|
|
350
|
-
getSqlType(type, isPrimaryKey = false) {
|
|
351
|
-
switch (type) {
|
|
352
|
-
case "text":
|
|
353
|
-
return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(MAX)";
|
|
354
|
-
case "timestamp":
|
|
355
|
-
return "DATETIME2(7)";
|
|
356
|
-
case "uuid":
|
|
357
|
-
return "UNIQUEIDENTIFIER";
|
|
358
|
-
case "jsonb":
|
|
359
|
-
return "NVARCHAR(MAX)";
|
|
360
|
-
case "integer":
|
|
361
|
-
return "INT";
|
|
362
|
-
case "bigint":
|
|
363
|
-
return "BIGINT";
|
|
364
|
-
default:
|
|
337
|
+
const mergedMetadata = {
|
|
338
|
+
...existingThread.metadata,
|
|
339
|
+
...metadata
|
|
340
|
+
};
|
|
341
|
+
try {
|
|
342
|
+
const table = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
|
|
343
|
+
const sql5 = `UPDATE ${table}
|
|
344
|
+
SET title = @title,
|
|
345
|
+
metadata = @metadata,
|
|
346
|
+
[updatedAt] = @updatedAt
|
|
347
|
+
OUTPUT INSERTED.*
|
|
348
|
+
WHERE id = @id`;
|
|
349
|
+
const req = this.pool.request();
|
|
350
|
+
req.input("id", id);
|
|
351
|
+
req.input("title", title);
|
|
352
|
+
req.input("metadata", JSON.stringify(mergedMetadata));
|
|
353
|
+
req.input("updatedAt", /* @__PURE__ */ new Date());
|
|
354
|
+
const result = await req.query(sql5);
|
|
355
|
+
let thread = result.recordset && result.recordset[0];
|
|
356
|
+
if (thread && "seq_id" in thread) {
|
|
357
|
+
const { seq_id, ...rest } = thread;
|
|
358
|
+
thread = rest;
|
|
359
|
+
}
|
|
360
|
+
if (!thread) {
|
|
365
361
|
throw new MastraError({
|
|
366
|
-
id: "
|
|
362
|
+
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
367
363
|
domain: ErrorDomain.STORAGE,
|
|
368
|
-
category: ErrorCategory.
|
|
364
|
+
category: ErrorCategory.USER,
|
|
365
|
+
text: `Thread ${id} not found after update`,
|
|
366
|
+
details: {
|
|
367
|
+
threadId: id,
|
|
368
|
+
title
|
|
369
|
+
}
|
|
369
370
|
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
async createTable({
|
|
373
|
-
tableName,
|
|
374
|
-
schema
|
|
375
|
-
}) {
|
|
376
|
-
try {
|
|
377
|
-
const uniqueConstraintColumns = tableName === TABLE_WORKFLOW_SNAPSHOT ? ["workflow_name", "run_id"] : [];
|
|
378
|
-
const columns = Object.entries(schema).map(([name, def]) => {
|
|
379
|
-
const parsedName = parseSqlIdentifier(name, "column name");
|
|
380
|
-
const constraints = [];
|
|
381
|
-
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
382
|
-
if (!def.nullable) constraints.push("NOT NULL");
|
|
383
|
-
const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
|
|
384
|
-
return `[${parsedName}] ${this.getSqlType(def.type, isIndexed)} ${constraints.join(" ")}`.trim();
|
|
385
|
-
}).join(",\n");
|
|
386
|
-
if (this.schema) {
|
|
387
|
-
await this.setupSchema();
|
|
388
|
-
}
|
|
389
|
-
const checkTableRequest = this.pool.request();
|
|
390
|
-
checkTableRequest.input("tableName", this.getTableName(tableName).replace(/[[\]]/g, "").split(".").pop());
|
|
391
|
-
const checkTableSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName`;
|
|
392
|
-
checkTableRequest.input("schema", this.schema || "dbo");
|
|
393
|
-
const checkTableResult = await checkTableRequest.query(checkTableSql);
|
|
394
|
-
const tableExists = Array.isArray(checkTableResult.recordset) && checkTableResult.recordset.length > 0;
|
|
395
|
-
if (!tableExists) {
|
|
396
|
-
const createSql = `CREATE TABLE ${this.getTableName(tableName)} (
|
|
397
|
-
${columns}
|
|
398
|
-
)`;
|
|
399
|
-
await this.pool.request().query(createSql);
|
|
400
|
-
}
|
|
401
|
-
const columnCheckSql = `
|
|
402
|
-
SELECT 1 AS found
|
|
403
|
-
FROM INFORMATION_SCHEMA.COLUMNS
|
|
404
|
-
WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = 'seq_id'
|
|
405
|
-
`;
|
|
406
|
-
const checkColumnRequest = this.pool.request();
|
|
407
|
-
checkColumnRequest.input("schema", this.schema || "dbo");
|
|
408
|
-
checkColumnRequest.input("tableName", this.getTableName(tableName).replace(/[[\]]/g, "").split(".").pop());
|
|
409
|
-
const columnResult = await checkColumnRequest.query(columnCheckSql);
|
|
410
|
-
const columnExists = Array.isArray(columnResult.recordset) && columnResult.recordset.length > 0;
|
|
411
|
-
if (!columnExists) {
|
|
412
|
-
const alterSql = `ALTER TABLE ${this.getTableName(tableName)} ADD seq_id BIGINT IDENTITY(1,1)`;
|
|
413
|
-
await this.pool.request().query(alterSql);
|
|
414
|
-
}
|
|
415
|
-
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
416
|
-
const constraintName = "mastra_workflow_snapshot_workflow_name_run_id_key";
|
|
417
|
-
const checkConstraintSql = `SELECT 1 AS found FROM sys.key_constraints WHERE name = @constraintName`;
|
|
418
|
-
const checkConstraintRequest = this.pool.request();
|
|
419
|
-
checkConstraintRequest.input("constraintName", constraintName);
|
|
420
|
-
const constraintResult = await checkConstraintRequest.query(checkConstraintSql);
|
|
421
|
-
const constraintExists = Array.isArray(constraintResult.recordset) && constraintResult.recordset.length > 0;
|
|
422
|
-
if (!constraintExists) {
|
|
423
|
-
const addConstraintSql = `ALTER TABLE ${this.getTableName(tableName)} ADD CONSTRAINT ${constraintName} UNIQUE ([workflow_name], [run_id])`;
|
|
424
|
-
await this.pool.request().query(addConstraintSql);
|
|
425
|
-
}
|
|
426
371
|
}
|
|
372
|
+
return {
|
|
373
|
+
...thread,
|
|
374
|
+
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
375
|
+
createdAt: thread.createdAt,
|
|
376
|
+
updatedAt: thread.updatedAt
|
|
377
|
+
};
|
|
427
378
|
} catch (error) {
|
|
428
379
|
throw new MastraError(
|
|
429
380
|
{
|
|
430
|
-
id: "
|
|
381
|
+
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "FAILED"),
|
|
431
382
|
domain: ErrorDomain.STORAGE,
|
|
432
383
|
category: ErrorCategory.THIRD_PARTY,
|
|
433
384
|
details: {
|
|
434
|
-
|
|
385
|
+
threadId: id,
|
|
386
|
+
title
|
|
435
387
|
}
|
|
436
388
|
},
|
|
437
389
|
error
|
|
438
390
|
);
|
|
439
391
|
}
|
|
440
392
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
default:
|
|
448
|
-
return super.getDefaultValue(type);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
async alterTable({
|
|
452
|
-
tableName,
|
|
453
|
-
schema,
|
|
454
|
-
ifNotExists
|
|
455
|
-
}) {
|
|
456
|
-
const fullTableName = this.getTableName(tableName);
|
|
393
|
+
async deleteThread({ threadId }) {
|
|
394
|
+
const messagesTable = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
|
|
395
|
+
const threadsTable = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
|
|
396
|
+
const deleteMessagesSql = `DELETE FROM ${messagesTable} WHERE [thread_id] = @threadId`;
|
|
397
|
+
const deleteThreadSql = `DELETE FROM ${threadsTable} WHERE id = @threadId`;
|
|
398
|
+
const tx = this.pool.transaction();
|
|
457
399
|
try {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const checkSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = @columnName`;
|
|
465
|
-
const checkResult = await columnCheckRequest.query(checkSql);
|
|
466
|
-
const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
|
|
467
|
-
if (!columnExists) {
|
|
468
|
-
const columnDef = schema[columnName];
|
|
469
|
-
const sqlType = this.getSqlType(columnDef.type);
|
|
470
|
-
const nullable = columnDef.nullable === false ? "NOT NULL" : "";
|
|
471
|
-
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
|
|
472
|
-
const parsedColumnName = parseSqlIdentifier(columnName, "column name");
|
|
473
|
-
const alterSql = `ALTER TABLE ${fullTableName} ADD [${parsedColumnName}] ${sqlType} ${nullable} ${defaultValue}`.trim();
|
|
474
|
-
await this.pool.request().query(alterSql);
|
|
475
|
-
this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
400
|
+
await tx.begin();
|
|
401
|
+
const req = tx.request();
|
|
402
|
+
req.input("threadId", threadId);
|
|
403
|
+
await req.query(deleteMessagesSql);
|
|
404
|
+
await req.query(deleteThreadSql);
|
|
405
|
+
await tx.commit();
|
|
479
406
|
} catch (error) {
|
|
407
|
+
await tx.rollback().catch(() => {
|
|
408
|
+
});
|
|
480
409
|
throw new MastraError(
|
|
481
410
|
{
|
|
482
|
-
id: "
|
|
411
|
+
id: createStorageErrorId("MSSQL", "DELETE_THREAD", "FAILED"),
|
|
483
412
|
domain: ErrorDomain.STORAGE,
|
|
484
413
|
category: ErrorCategory.THIRD_PARTY,
|
|
485
414
|
details: {
|
|
486
|
-
|
|
415
|
+
threadId
|
|
487
416
|
}
|
|
488
417
|
},
|
|
489
418
|
error
|
|
490
419
|
);
|
|
491
420
|
}
|
|
492
421
|
}
|
|
493
|
-
async
|
|
494
|
-
|
|
422
|
+
async _getIncludedMessages({ include }) {
|
|
423
|
+
if (!include || include.length === 0) return null;
|
|
424
|
+
const unionQueries = [];
|
|
425
|
+
const paramValues = [];
|
|
426
|
+
let paramIdx = 1;
|
|
427
|
+
const paramNames = [];
|
|
428
|
+
const tableName = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
|
|
429
|
+
for (const inc of include) {
|
|
430
|
+
const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
|
|
431
|
+
const pId = `@p${paramIdx}`;
|
|
432
|
+
const pPrev = `@p${paramIdx + 1}`;
|
|
433
|
+
const pNext = `@p${paramIdx + 2}`;
|
|
434
|
+
unionQueries.push(
|
|
435
|
+
`
|
|
436
|
+
SELECT
|
|
437
|
+
m.id,
|
|
438
|
+
m.content,
|
|
439
|
+
m.role,
|
|
440
|
+
m.type,
|
|
441
|
+
m.[createdAt],
|
|
442
|
+
m.thread_id AS threadId,
|
|
443
|
+
m.[resourceId],
|
|
444
|
+
m.seq_id
|
|
445
|
+
FROM (
|
|
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})
|
|
449
|
+
) AS m
|
|
450
|
+
WHERE m.id = ${pId}
|
|
451
|
+
OR EXISTS (
|
|
452
|
+
SELECT 1
|
|
453
|
+
FROM (
|
|
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})
|
|
457
|
+
) AS target
|
|
458
|
+
WHERE target.id = ${pId}
|
|
459
|
+
AND (
|
|
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})
|
|
462
|
+
OR
|
|
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})
|
|
465
|
+
)
|
|
466
|
+
)
|
|
467
|
+
`
|
|
468
|
+
);
|
|
469
|
+
paramValues.push(id, withPreviousMessages, withNextMessages);
|
|
470
|
+
paramNames.push(`p${paramIdx}`, `p${paramIdx + 1}`, `p${paramIdx + 2}`);
|
|
471
|
+
paramIdx += 3;
|
|
472
|
+
}
|
|
473
|
+
const finalQuery = `
|
|
474
|
+
SELECT * FROM (
|
|
475
|
+
${unionQueries.join(" UNION ALL ")}
|
|
476
|
+
) AS union_result
|
|
477
|
+
ORDER BY [seq_id] ASC
|
|
478
|
+
`;
|
|
479
|
+
const req = this.pool.request();
|
|
480
|
+
for (let i = 0; i < paramValues.length; ++i) {
|
|
481
|
+
req.input(paramNames[i], paramValues[i]);
|
|
482
|
+
}
|
|
483
|
+
const result = await req.query(finalQuery);
|
|
484
|
+
const includedRows = result.recordset || [];
|
|
485
|
+
const seen = /* @__PURE__ */ new Set();
|
|
486
|
+
const dedupedRows = includedRows.filter((row) => {
|
|
487
|
+
if (seen.has(row.id)) return false;
|
|
488
|
+
seen.add(row.id);
|
|
489
|
+
return true;
|
|
490
|
+
});
|
|
491
|
+
return dedupedRows;
|
|
492
|
+
}
|
|
493
|
+
async listMessagesById({ messageIds }) {
|
|
494
|
+
if (messageIds.length === 0) return { messages: [] };
|
|
495
|
+
const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
|
|
496
|
+
const orderByStatement = `ORDER BY [seq_id] DESC`;
|
|
495
497
|
try {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
498
|
+
let rows = [];
|
|
499
|
+
let query = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE [id] IN (${messageIds.map((_, i) => `@id${i}`).join(", ")})`;
|
|
500
|
+
const request = this.pool.request();
|
|
501
|
+
messageIds.forEach((id, i) => request.input(`id${i}`, id));
|
|
502
|
+
query += ` ${orderByStatement}`;
|
|
503
|
+
const result = await request.query(query);
|
|
504
|
+
const remainingRows = result.recordset || [];
|
|
505
|
+
rows.push(...remainingRows);
|
|
506
|
+
rows.sort((a, b) => {
|
|
507
|
+
const timeDiff = a.seq_id - b.seq_id;
|
|
508
|
+
return timeDiff;
|
|
509
|
+
});
|
|
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() };
|
|
510
523
|
} catch (error) {
|
|
511
|
-
|
|
524
|
+
const mastraError = new MastraError(
|
|
512
525
|
{
|
|
513
|
-
id: "
|
|
526
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES_BY_ID", "FAILED"),
|
|
514
527
|
domain: ErrorDomain.STORAGE,
|
|
515
528
|
category: ErrorCategory.THIRD_PARTY,
|
|
516
529
|
details: {
|
|
517
|
-
|
|
530
|
+
messageIds: JSON.stringify(messageIds)
|
|
518
531
|
}
|
|
519
532
|
},
|
|
520
533
|
error
|
|
521
534
|
);
|
|
535
|
+
this.logger?.error?.(mastraError.toString());
|
|
536
|
+
this.logger?.trackException?.(mastraError);
|
|
537
|
+
return { messages: [] };
|
|
522
538
|
}
|
|
523
539
|
}
|
|
524
|
-
async
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const paramNames = values.map((_, i) => `@param${i}`);
|
|
529
|
-
const insertSql = `INSERT INTO ${this.getTableName(tableName)} (${columns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
|
|
530
|
-
const request = this.pool.request();
|
|
531
|
-
values.forEach((value, i) => {
|
|
532
|
-
if (value instanceof Date) {
|
|
533
|
-
request.input(`param${i}`, sql.DateTime2, value);
|
|
534
|
-
} else if (typeof value === "object" && value !== null) {
|
|
535
|
-
request.input(`param${i}`, JSON.stringify(value));
|
|
536
|
-
} else {
|
|
537
|
-
request.input(`param${i}`, value);
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
await request.query(insertSql);
|
|
541
|
-
} catch (error) {
|
|
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())) {
|
|
542
544
|
throw new MastraError(
|
|
543
545
|
{
|
|
544
|
-
id: "
|
|
546
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES", "INVALID_THREAD_ID"),
|
|
545
547
|
domain: ErrorDomain.STORAGE,
|
|
546
548
|
category: ErrorCategory.THIRD_PARTY,
|
|
547
|
-
details: {
|
|
548
|
-
tableName
|
|
549
|
-
}
|
|
549
|
+
details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
|
|
550
550
|
},
|
|
551
|
-
|
|
551
|
+
new Error("threadId must be a non-empty string or array of non-empty strings")
|
|
552
552
|
);
|
|
553
553
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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);
|
|
568
|
+
try {
|
|
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}`);
|
|
586
|
+
const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
|
|
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 ? sql2.BigInt : sql2.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)) {
|
|
608
|
+
return {
|
|
609
|
+
messages: [],
|
|
610
|
+
total: 0,
|
|
611
|
+
page,
|
|
612
|
+
perPage: perPageForResponse,
|
|
613
|
+
hasMore: false
|
|
614
|
+
};
|
|
615
|
+
}
|
|
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;
|
|
644
|
+
return {
|
|
645
|
+
messages: finalMessages,
|
|
646
|
+
total,
|
|
647
|
+
page,
|
|
648
|
+
perPage: perPageForResponse,
|
|
649
|
+
hasMore
|
|
650
|
+
};
|
|
651
|
+
} catch (error) {
|
|
652
|
+
const mastraError = new MastraError(
|
|
653
|
+
{
|
|
654
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES", "FAILED"),
|
|
655
|
+
domain: ErrorDomain.STORAGE,
|
|
656
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
657
|
+
details: {
|
|
658
|
+
threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
|
|
659
|
+
resourceId: resourceId ?? ""
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
error
|
|
663
|
+
);
|
|
664
|
+
this.logger?.error?.(mastraError.toString());
|
|
665
|
+
this.logger?.trackException?.(mastraError);
|
|
666
|
+
return {
|
|
667
|
+
messages: [],
|
|
668
|
+
total: 0,
|
|
669
|
+
page,
|
|
670
|
+
perPage: perPageForResponse,
|
|
671
|
+
hasMore: false
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
async saveMessages({ messages }) {
|
|
676
|
+
if (messages.length === 0) return { messages: [] };
|
|
677
|
+
const threadId = messages[0]?.threadId;
|
|
678
|
+
if (!threadId) {
|
|
679
|
+
throw new MastraError({
|
|
680
|
+
id: createStorageErrorId("MSSQL", "SAVE_MESSAGES", "INVALID_THREAD_ID"),
|
|
681
|
+
domain: ErrorDomain.STORAGE,
|
|
682
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
683
|
+
text: `Thread ID is required`
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
const thread = await this.getThreadById({ threadId });
|
|
687
|
+
if (!thread) {
|
|
688
|
+
throw new MastraError({
|
|
689
|
+
id: createStorageErrorId("MSSQL", "SAVE_MESSAGES", "THREAD_NOT_FOUND"),
|
|
690
|
+
domain: ErrorDomain.STORAGE,
|
|
691
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
692
|
+
text: `Thread ${threadId} not found`,
|
|
693
|
+
details: { threadId }
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
const tableMessages = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
|
|
697
|
+
const tableThreads = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
|
|
698
|
+
try {
|
|
699
|
+
const transaction = this.pool.transaction();
|
|
700
|
+
await transaction.begin();
|
|
701
|
+
try {
|
|
702
|
+
for (const message of messages) {
|
|
703
|
+
if (!message.threadId) {
|
|
704
|
+
throw new Error(
|
|
705
|
+
`Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
if (!message.resourceId) {
|
|
709
|
+
throw new Error(
|
|
710
|
+
`Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
const request = transaction.request();
|
|
714
|
+
request.input("id", message.id);
|
|
715
|
+
request.input("thread_id", message.threadId);
|
|
716
|
+
request.input(
|
|
717
|
+
"content",
|
|
718
|
+
typeof message.content === "string" ? message.content : JSON.stringify(message.content)
|
|
719
|
+
);
|
|
720
|
+
request.input("createdAt", sql2.DateTime2, message.createdAt);
|
|
721
|
+
request.input("role", message.role);
|
|
722
|
+
request.input("type", message.type || "v2");
|
|
723
|
+
request.input("resourceId", message.resourceId);
|
|
724
|
+
const mergeSql = `MERGE INTO ${tableMessages} AS target
|
|
725
|
+
USING (SELECT @id AS id) AS src
|
|
726
|
+
ON target.id = src.id
|
|
727
|
+
WHEN MATCHED THEN UPDATE SET
|
|
728
|
+
thread_id = @thread_id,
|
|
729
|
+
content = @content,
|
|
730
|
+
[createdAt] = @createdAt,
|
|
731
|
+
role = @role,
|
|
732
|
+
type = @type,
|
|
733
|
+
resourceId = @resourceId
|
|
734
|
+
WHEN NOT MATCHED THEN INSERT (id, thread_id, content, [createdAt], role, type, resourceId)
|
|
735
|
+
VALUES (@id, @thread_id, @content, @createdAt, @role, @type, @resourceId);`;
|
|
736
|
+
await request.query(mergeSql);
|
|
737
|
+
}
|
|
738
|
+
const threadReq = transaction.request();
|
|
739
|
+
threadReq.input("updatedAt", sql2.DateTime2, /* @__PURE__ */ new Date());
|
|
740
|
+
threadReq.input("id", threadId);
|
|
741
|
+
await threadReq.query(`UPDATE ${tableThreads} SET [updatedAt] = @updatedAt WHERE id = @id`);
|
|
742
|
+
await transaction.commit();
|
|
743
|
+
} catch (error) {
|
|
744
|
+
await transaction.rollback();
|
|
745
|
+
throw error;
|
|
746
|
+
}
|
|
747
|
+
const messagesWithParsedContent = messages.map((message) => {
|
|
748
|
+
if (typeof message.content === "string") {
|
|
749
|
+
try {
|
|
750
|
+
return { ...message, content: JSON.parse(message.content) };
|
|
751
|
+
} catch {
|
|
752
|
+
return message;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return message;
|
|
756
|
+
});
|
|
757
|
+
const list = new MessageList().add(messagesWithParsedContent, "memory");
|
|
758
|
+
return { messages: list.get.all.db() };
|
|
759
|
+
} catch (error) {
|
|
760
|
+
throw new MastraError(
|
|
761
|
+
{
|
|
762
|
+
id: createStorageErrorId("MSSQL", "SAVE_MESSAGES", "FAILED"),
|
|
763
|
+
domain: ErrorDomain.STORAGE,
|
|
764
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
765
|
+
details: { threadId }
|
|
766
|
+
},
|
|
767
|
+
error
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
async updateMessages({
|
|
772
|
+
messages
|
|
773
|
+
}) {
|
|
774
|
+
if (!messages || messages.length === 0) {
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
const messageIds = messages.map((m) => m.id);
|
|
778
|
+
const idParams = messageIds.map((_, i) => `@id${i}`).join(", ");
|
|
779
|
+
let selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}`;
|
|
780
|
+
if (idParams.length > 0) {
|
|
781
|
+
selectQuery += ` WHERE id IN (${idParams})`;
|
|
782
|
+
} else {
|
|
783
|
+
return [];
|
|
784
|
+
}
|
|
785
|
+
const selectReq = this.pool.request();
|
|
786
|
+
messageIds.forEach((id, i) => selectReq.input(`id${i}`, id));
|
|
787
|
+
const existingMessagesDb = (await selectReq.query(selectQuery)).recordset;
|
|
788
|
+
if (!existingMessagesDb || existingMessagesDb.length === 0) {
|
|
789
|
+
return [];
|
|
790
|
+
}
|
|
791
|
+
const existingMessages = existingMessagesDb.map((msg) => {
|
|
792
|
+
if (typeof msg.content === "string") {
|
|
793
|
+
try {
|
|
794
|
+
msg.content = JSON.parse(msg.content);
|
|
795
|
+
} catch {
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return msg;
|
|
799
|
+
});
|
|
800
|
+
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
801
|
+
const transaction = this.pool.transaction();
|
|
802
|
+
try {
|
|
803
|
+
await transaction.begin();
|
|
804
|
+
for (const existingMessage of existingMessages) {
|
|
805
|
+
const updatePayload = messages.find((m) => m.id === existingMessage.id);
|
|
806
|
+
if (!updatePayload) continue;
|
|
807
|
+
const { id, ...fieldsToUpdate } = updatePayload;
|
|
808
|
+
if (Object.keys(fieldsToUpdate).length === 0) continue;
|
|
809
|
+
threadIdsToUpdate.add(existingMessage.threadId);
|
|
810
|
+
if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
|
|
811
|
+
threadIdsToUpdate.add(updatePayload.threadId);
|
|
812
|
+
}
|
|
813
|
+
const setClauses = [];
|
|
814
|
+
const req = transaction.request();
|
|
815
|
+
req.input("id", id);
|
|
816
|
+
const columnMapping = { threadId: "thread_id" };
|
|
817
|
+
const updatableFields = { ...fieldsToUpdate };
|
|
818
|
+
if (updatableFields.content) {
|
|
819
|
+
const newContent = {
|
|
820
|
+
...existingMessage.content,
|
|
821
|
+
...updatableFields.content,
|
|
822
|
+
...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: { ...existingMessage.content.metadata, ...updatableFields.content.metadata } } : {}
|
|
823
|
+
};
|
|
824
|
+
setClauses.push(`content = @content`);
|
|
825
|
+
req.input("content", JSON.stringify(newContent));
|
|
826
|
+
delete updatableFields.content;
|
|
827
|
+
}
|
|
828
|
+
for (const key in updatableFields) {
|
|
829
|
+
if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
|
|
830
|
+
const dbColumn = columnMapping[key] || key;
|
|
831
|
+
setClauses.push(`[${dbColumn}] = @${dbColumn}`);
|
|
832
|
+
req.input(dbColumn, updatableFields[key]);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (setClauses.length > 0) {
|
|
836
|
+
const updateSql = `UPDATE ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} SET ${setClauses.join(", ")} WHERE id = @id`;
|
|
837
|
+
await req.query(updateSql);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (threadIdsToUpdate.size > 0) {
|
|
841
|
+
const threadIdParams = Array.from(threadIdsToUpdate).map((_, i) => `@tid${i}`).join(", ");
|
|
842
|
+
const threadReq = transaction.request();
|
|
843
|
+
Array.from(threadIdsToUpdate).forEach((tid, i) => threadReq.input(`tid${i}`, tid));
|
|
844
|
+
threadReq.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
|
|
845
|
+
const threadSql = `UPDATE ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} SET updatedAt = @updatedAt WHERE id IN (${threadIdParams})`;
|
|
846
|
+
await threadReq.query(threadSql);
|
|
847
|
+
}
|
|
848
|
+
await transaction.commit();
|
|
849
|
+
} catch (error) {
|
|
850
|
+
await transaction.rollback();
|
|
851
|
+
throw new MastraError(
|
|
852
|
+
{
|
|
853
|
+
id: createStorageErrorId("MSSQL", "UPDATE_MESSAGES", "FAILED"),
|
|
854
|
+
domain: ErrorDomain.STORAGE,
|
|
855
|
+
category: ErrorCategory.THIRD_PARTY
|
|
856
|
+
},
|
|
857
|
+
error
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
const refetchReq = this.pool.request();
|
|
861
|
+
messageIds.forEach((id, i) => refetchReq.input(`id${i}`, id));
|
|
862
|
+
const updatedMessages = (await refetchReq.query(selectQuery)).recordset;
|
|
863
|
+
return (updatedMessages || []).map((message) => {
|
|
864
|
+
if (typeof message.content === "string") {
|
|
865
|
+
try {
|
|
866
|
+
message.content = JSON.parse(message.content);
|
|
867
|
+
} catch {
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return message;
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
async deleteMessages(messageIds) {
|
|
874
|
+
if (!messageIds || messageIds.length === 0) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
try {
|
|
878
|
+
const messageTableName = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
|
|
879
|
+
const threadTableName = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
|
|
880
|
+
const placeholders = messageIds.map((_, idx) => `@p${idx + 1}`).join(",");
|
|
881
|
+
const request = this.pool.request();
|
|
882
|
+
messageIds.forEach((id, idx) => {
|
|
883
|
+
request.input(`p${idx + 1}`, id);
|
|
884
|
+
});
|
|
885
|
+
const messages = await request.query(
|
|
886
|
+
`SELECT DISTINCT [thread_id] FROM ${messageTableName} WHERE [id] IN (${placeholders})`
|
|
887
|
+
);
|
|
888
|
+
const threadIds = messages.recordset?.map((msg) => msg.thread_id).filter(Boolean) || [];
|
|
889
|
+
const transaction = this.pool.transaction();
|
|
890
|
+
await transaction.begin();
|
|
891
|
+
try {
|
|
892
|
+
const deleteRequest = transaction.request();
|
|
893
|
+
messageIds.forEach((id, idx) => {
|
|
894
|
+
deleteRequest.input(`p${idx + 1}`, id);
|
|
895
|
+
});
|
|
896
|
+
await deleteRequest.query(`DELETE FROM ${messageTableName} WHERE [id] IN (${placeholders})`);
|
|
897
|
+
if (threadIds.length > 0) {
|
|
898
|
+
for (const threadId of threadIds) {
|
|
899
|
+
const updateRequest = transaction.request();
|
|
900
|
+
updateRequest.input("p1", threadId);
|
|
901
|
+
await updateRequest.query(`UPDATE ${threadTableName} SET [updatedAt] = GETDATE() WHERE [id] = @p1`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
await transaction.commit();
|
|
905
|
+
} catch (error) {
|
|
906
|
+
try {
|
|
907
|
+
await transaction.rollback();
|
|
908
|
+
} catch {
|
|
909
|
+
}
|
|
910
|
+
throw error;
|
|
911
|
+
}
|
|
912
|
+
} catch (error) {
|
|
913
|
+
throw new MastraError(
|
|
914
|
+
{
|
|
915
|
+
id: createStorageErrorId("MSSQL", "DELETE_MESSAGES", "FAILED"),
|
|
916
|
+
domain: ErrorDomain.STORAGE,
|
|
917
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
918
|
+
details: { messageIds: messageIds.join(", ") }
|
|
919
|
+
},
|
|
920
|
+
error
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
async getResourceById({ resourceId }) {
|
|
925
|
+
const tableName = getTableName({ indexName: TABLE_RESOURCES, schemaName: getSchemaName(this.schema) });
|
|
926
|
+
try {
|
|
927
|
+
const req = this.pool.request();
|
|
928
|
+
req.input("resourceId", resourceId);
|
|
929
|
+
const result = (await req.query(`SELECT * FROM ${tableName} WHERE id = @resourceId`)).recordset[0];
|
|
930
|
+
if (!result) {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
return {
|
|
934
|
+
id: result.id,
|
|
935
|
+
createdAt: result.createdAt,
|
|
936
|
+
updatedAt: result.updatedAt,
|
|
937
|
+
workingMemory: result.workingMemory,
|
|
938
|
+
metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
|
|
939
|
+
};
|
|
940
|
+
} catch (error) {
|
|
941
|
+
const mastraError = new MastraError(
|
|
942
|
+
{
|
|
943
|
+
id: createStorageErrorId("MSSQL", "GET_RESOURCE_BY_ID", "FAILED"),
|
|
944
|
+
domain: ErrorDomain.STORAGE,
|
|
945
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
946
|
+
details: { resourceId }
|
|
947
|
+
},
|
|
948
|
+
error
|
|
949
|
+
);
|
|
950
|
+
this.logger?.error?.(mastraError.toString());
|
|
951
|
+
this.logger?.trackException?.(mastraError);
|
|
952
|
+
throw mastraError;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
async saveResource({ resource }) {
|
|
956
|
+
await this.operations.insert({
|
|
957
|
+
tableName: TABLE_RESOURCES,
|
|
958
|
+
record: {
|
|
959
|
+
...resource,
|
|
960
|
+
metadata: resource.metadata
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
return resource;
|
|
964
|
+
}
|
|
965
|
+
async updateResource({
|
|
966
|
+
resourceId,
|
|
967
|
+
workingMemory,
|
|
968
|
+
metadata
|
|
969
|
+
}) {
|
|
970
|
+
try {
|
|
971
|
+
const existingResource = await this.getResourceById({ resourceId });
|
|
972
|
+
if (!existingResource) {
|
|
973
|
+
const newResource = {
|
|
974
|
+
id: resourceId,
|
|
975
|
+
workingMemory,
|
|
976
|
+
metadata: metadata || {},
|
|
977
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
978
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
979
|
+
};
|
|
980
|
+
return this.saveResource({ resource: newResource });
|
|
981
|
+
}
|
|
982
|
+
const updatedResource = {
|
|
983
|
+
...existingResource,
|
|
984
|
+
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
985
|
+
metadata: {
|
|
986
|
+
...existingResource.metadata,
|
|
987
|
+
...metadata
|
|
988
|
+
},
|
|
989
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
990
|
+
};
|
|
991
|
+
const tableName = getTableName({ indexName: TABLE_RESOURCES, schemaName: getSchemaName(this.schema) });
|
|
992
|
+
const updates = [];
|
|
993
|
+
const req = this.pool.request();
|
|
994
|
+
if (workingMemory !== void 0) {
|
|
995
|
+
updates.push("workingMemory = @workingMemory");
|
|
996
|
+
req.input("workingMemory", workingMemory);
|
|
997
|
+
}
|
|
998
|
+
if (metadata) {
|
|
999
|
+
updates.push("metadata = @metadata");
|
|
1000
|
+
req.input("metadata", JSON.stringify(updatedResource.metadata));
|
|
1001
|
+
}
|
|
1002
|
+
updates.push("updatedAt = @updatedAt");
|
|
1003
|
+
req.input("updatedAt", updatedResource.updatedAt.toISOString());
|
|
1004
|
+
req.input("id", resourceId);
|
|
1005
|
+
await req.query(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = @id`);
|
|
1006
|
+
return updatedResource;
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
const mastraError = new MastraError(
|
|
1009
|
+
{
|
|
1010
|
+
id: createStorageErrorId("MSSQL", "UPDATE_RESOURCE", "FAILED"),
|
|
1011
|
+
domain: ErrorDomain.STORAGE,
|
|
1012
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1013
|
+
details: { resourceId }
|
|
1014
|
+
},
|
|
1015
|
+
error
|
|
1016
|
+
);
|
|
1017
|
+
this.logger?.error?.(mastraError.toString());
|
|
1018
|
+
this.logger?.trackException?.(mastraError);
|
|
1019
|
+
throw mastraError;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
var ObservabilityMSSQL = class extends ObservabilityStorage {
|
|
1024
|
+
pool;
|
|
1025
|
+
operations;
|
|
1026
|
+
schema;
|
|
1027
|
+
constructor({
|
|
1028
|
+
pool,
|
|
1029
|
+
operations,
|
|
1030
|
+
schema
|
|
1031
|
+
}) {
|
|
1032
|
+
super();
|
|
1033
|
+
this.pool = pool;
|
|
1034
|
+
this.operations = operations;
|
|
1035
|
+
this.schema = schema;
|
|
1036
|
+
}
|
|
1037
|
+
get tracingStrategy() {
|
|
1038
|
+
return {
|
|
1039
|
+
preferred: "batch-with-updates",
|
|
1040
|
+
supported: ["batch-with-updates", "insert-only"]
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
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
|
+
);
|
|
1069
|
+
}
|
|
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) {
|
|
1393
|
+
this.setupSchemaPromise = (async () => {
|
|
1394
|
+
try {
|
|
1395
|
+
const checkRequest = this.pool.request();
|
|
1396
|
+
checkRequest.input("schemaName", this.schemaName);
|
|
1397
|
+
const checkResult = await checkRequest.query(`
|
|
1398
|
+
SELECT 1 AS found FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = @schemaName
|
|
1399
|
+
`);
|
|
1400
|
+
const schemaExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
|
|
1401
|
+
if (!schemaExists) {
|
|
1402
|
+
try {
|
|
1403
|
+
await this.pool.request().query(`CREATE SCHEMA [${this.schemaName}]`);
|
|
1404
|
+
this.logger?.info?.(`Schema "${this.schemaName}" created successfully`);
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
this.logger?.error?.(`Failed to create schema "${this.schemaName}"`, { error });
|
|
1407
|
+
throw new Error(
|
|
1408
|
+
`Unable to create schema "${this.schemaName}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
this.schemaSetupComplete = true;
|
|
1413
|
+
this.logger?.debug?.(`Schema "${this.schemaName}" is ready for use`);
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
this.schemaSetupComplete = void 0;
|
|
1416
|
+
this.setupSchemaPromise = null;
|
|
1417
|
+
throw error;
|
|
1418
|
+
} finally {
|
|
1419
|
+
this.setupSchemaPromise = null;
|
|
1420
|
+
}
|
|
1421
|
+
})();
|
|
1422
|
+
}
|
|
1423
|
+
await this.setupSchemaPromise;
|
|
1424
|
+
}
|
|
1425
|
+
async insert({
|
|
1426
|
+
tableName,
|
|
1427
|
+
record,
|
|
1428
|
+
transaction
|
|
1429
|
+
}) {
|
|
1430
|
+
try {
|
|
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}`, sql2.DateTime2, preparedValue);
|
|
1441
|
+
} else if (preparedValue === null || preparedValue === void 0) {
|
|
1442
|
+
request.input(`param${i}`, this.getMssqlType(tableName, col), null);
|
|
1443
|
+
} else {
|
|
1444
|
+
request.input(`param${i}`, preparedValue);
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
await request.query(insertSql);
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
throw new MastraError(
|
|
1450
|
+
{
|
|
1451
|
+
id: createStorageErrorId("MSSQL", "INSERT", "FAILED"),
|
|
1452
|
+
domain: ErrorDomain.STORAGE,
|
|
1453
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1454
|
+
details: {
|
|
1455
|
+
tableName
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
error
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
async clearTable({ tableName }) {
|
|
1463
|
+
const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
|
|
1464
|
+
try {
|
|
1465
|
+
try {
|
|
1466
|
+
await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
|
|
1467
|
+
} catch (truncateError) {
|
|
1468
|
+
if (truncateError?.number === 4712) {
|
|
1469
|
+
await this.pool.request().query(`DELETE FROM ${fullTableName}`);
|
|
1470
|
+
} else {
|
|
1471
|
+
throw truncateError;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
throw new MastraError(
|
|
1476
|
+
{
|
|
1477
|
+
id: createStorageErrorId("MSSQL", "CLEAR_TABLE", "FAILED"),
|
|
1478
|
+
domain: ErrorDomain.STORAGE,
|
|
1479
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1480
|
+
details: {
|
|
1481
|
+
tableName
|
|
1482
|
+
}
|
|
1483
|
+
},
|
|
1484
|
+
error
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
getDefaultValue(type) {
|
|
1489
|
+
switch (type) {
|
|
1490
|
+
case "timestamp":
|
|
1491
|
+
return "DEFAULT SYSUTCDATETIME()";
|
|
1492
|
+
case "jsonb":
|
|
1493
|
+
return "DEFAULT N'{}'";
|
|
1494
|
+
case "boolean":
|
|
1495
|
+
return "DEFAULT 0";
|
|
1496
|
+
default:
|
|
1497
|
+
return super.getDefaultValue(type);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
async createTable({
|
|
1501
|
+
tableName,
|
|
1502
|
+
schema
|
|
1503
|
+
}) {
|
|
1504
|
+
try {
|
|
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
|
+
];
|
|
1521
|
+
const columns = Object.entries(schema).map(([name, def]) => {
|
|
1522
|
+
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1523
|
+
const constraints = [];
|
|
1524
|
+
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
1525
|
+
if (!def.nullable) constraints.push("NOT NULL");
|
|
1526
|
+
const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
|
|
1527
|
+
const useLargeStorage = largeDataColumns.includes(name);
|
|
1528
|
+
return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
|
|
1529
|
+
}).join(",\n");
|
|
1530
|
+
if (this.schemaName) {
|
|
1531
|
+
await this.setupSchema();
|
|
1532
|
+
}
|
|
1533
|
+
const checkTableRequest = this.pool.request();
|
|
1534
|
+
checkTableRequest.input(
|
|
1535
|
+
"tableName",
|
|
1536
|
+
getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
|
|
1537
|
+
);
|
|
1538
|
+
const checkTableSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName`;
|
|
1539
|
+
checkTableRequest.input("schema", this.schemaName || "dbo");
|
|
1540
|
+
const checkTableResult = await checkTableRequest.query(checkTableSql);
|
|
1541
|
+
const tableExists = Array.isArray(checkTableResult.recordset) && checkTableResult.recordset.length > 0;
|
|
1542
|
+
if (!tableExists) {
|
|
1543
|
+
const createSql = `CREATE TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (
|
|
1544
|
+
${columns}
|
|
1545
|
+
)`;
|
|
1546
|
+
await this.pool.request().query(createSql);
|
|
1547
|
+
}
|
|
1548
|
+
const columnCheckSql = `
|
|
1549
|
+
SELECT 1 AS found
|
|
1550
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
1551
|
+
WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = 'seq_id'
|
|
1552
|
+
`;
|
|
1553
|
+
const checkColumnRequest = this.pool.request();
|
|
1554
|
+
checkColumnRequest.input("schema", this.schemaName || "dbo");
|
|
1555
|
+
checkColumnRequest.input(
|
|
1556
|
+
"tableName",
|
|
1557
|
+
getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
|
|
1558
|
+
);
|
|
1559
|
+
const columnResult = await checkColumnRequest.query(columnCheckSql);
|
|
1560
|
+
const columnExists = Array.isArray(columnResult.recordset) && columnResult.recordset.length > 0;
|
|
1561
|
+
if (!columnExists) {
|
|
1562
|
+
const alterSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD seq_id BIGINT IDENTITY(1,1)`;
|
|
1563
|
+
await this.pool.request().query(alterSql);
|
|
1564
|
+
}
|
|
1565
|
+
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
1566
|
+
const constraintName = "mastra_workflow_snapshot_workflow_name_run_id_key";
|
|
1567
|
+
const checkConstraintSql = `SELECT 1 AS found FROM sys.key_constraints WHERE name = @constraintName`;
|
|
1568
|
+
const checkConstraintRequest = this.pool.request();
|
|
1569
|
+
checkConstraintRequest.input("constraintName", constraintName);
|
|
1570
|
+
const constraintResult = await checkConstraintRequest.query(checkConstraintSql);
|
|
1571
|
+
const constraintExists = Array.isArray(constraintResult.recordset) && constraintResult.recordset.length > 0;
|
|
1572
|
+
if (!constraintExists) {
|
|
1573
|
+
const addConstraintSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD CONSTRAINT ${constraintName} UNIQUE ([workflow_name], [run_id])`;
|
|
1574
|
+
await this.pool.request().query(addConstraintSql);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
throw new MastraError(
|
|
1579
|
+
{
|
|
1580
|
+
id: createStorageErrorId("MSSQL", "CREATE_TABLE", "FAILED"),
|
|
1581
|
+
domain: ErrorDomain.STORAGE,
|
|
1582
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1583
|
+
details: {
|
|
1584
|
+
tableName
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
error
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Alters table schema to add columns if they don't exist
|
|
1593
|
+
* @param tableName Name of the table
|
|
1594
|
+
* @param schema Schema of the table
|
|
1595
|
+
* @param ifNotExists Array of column names to add if they don't exist
|
|
1596
|
+
*/
|
|
1597
|
+
async alterTable({
|
|
1598
|
+
tableName,
|
|
1599
|
+
schema,
|
|
1600
|
+
ifNotExists
|
|
1601
|
+
}) {
|
|
1602
|
+
const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
|
|
1603
|
+
try {
|
|
1604
|
+
for (const columnName of ifNotExists) {
|
|
1605
|
+
if (schema[columnName]) {
|
|
1606
|
+
const columnCheckRequest = this.pool.request();
|
|
1607
|
+
columnCheckRequest.input("tableName", fullTableName.replace(/[[\]]/g, "").split(".").pop());
|
|
1608
|
+
columnCheckRequest.input("columnName", columnName);
|
|
1609
|
+
columnCheckRequest.input("schema", this.schemaName || "dbo");
|
|
1610
|
+
const checkSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = @columnName`;
|
|
1611
|
+
const checkResult = await columnCheckRequest.query(checkSql);
|
|
1612
|
+
const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
|
|
1613
|
+
if (!columnExists) {
|
|
1614
|
+
const columnDef = schema[columnName];
|
|
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);
|
|
1628
|
+
const nullable = columnDef.nullable === false ? "NOT NULL" : "";
|
|
1629
|
+
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
|
|
1630
|
+
const parsedColumnName = parseSqlIdentifier(columnName, "column name");
|
|
1631
|
+
const alterSql = `ALTER TABLE ${fullTableName} ADD [${parsedColumnName}] ${sqlType} ${nullable} ${defaultValue}`.trim();
|
|
1632
|
+
await this.pool.request().query(alterSql);
|
|
1633
|
+
this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
} catch (error) {
|
|
1638
|
+
throw new MastraError(
|
|
1639
|
+
{
|
|
1640
|
+
id: createStorageErrorId("MSSQL", "ALTER_TABLE", "FAILED"),
|
|
1641
|
+
domain: ErrorDomain.STORAGE,
|
|
1642
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1643
|
+
details: {
|
|
1644
|
+
tableName
|
|
1645
|
+
}
|
|
1646
|
+
},
|
|
1647
|
+
error
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
async load({ tableName, keys }) {
|
|
1652
|
+
try {
|
|
1653
|
+
const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
|
|
1654
|
+
const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
|
|
1655
|
+
const sql5 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
|
|
1656
|
+
const request = this.pool.request();
|
|
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
|
+
}
|
|
564
1664
|
});
|
|
565
|
-
const resultSet = await request.query(
|
|
1665
|
+
const resultSet = await request.query(sql5);
|
|
566
1666
|
const result = resultSet.recordset[0] || null;
|
|
567
1667
|
if (!result) {
|
|
568
1668
|
return null;
|
|
569
1669
|
}
|
|
570
|
-
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
571
|
-
const snapshot = result;
|
|
572
|
-
if (typeof snapshot.snapshot === "string") {
|
|
573
|
-
snapshot.snapshot = JSON.parse(snapshot.snapshot);
|
|
574
|
-
}
|
|
575
|
-
return snapshot;
|
|
1670
|
+
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
1671
|
+
const snapshot = result;
|
|
1672
|
+
if (typeof snapshot.snapshot === "string") {
|
|
1673
|
+
snapshot.snapshot = JSON.parse(snapshot.snapshot);
|
|
1674
|
+
}
|
|
1675
|
+
return snapshot;
|
|
1676
|
+
}
|
|
1677
|
+
return result;
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
throw new MastraError(
|
|
1680
|
+
{
|
|
1681
|
+
id: createStorageErrorId("MSSQL", "LOAD", "FAILED"),
|
|
1682
|
+
domain: ErrorDomain.STORAGE,
|
|
1683
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1684
|
+
details: {
|
|
1685
|
+
tableName
|
|
1686
|
+
}
|
|
1687
|
+
},
|
|
1688
|
+
error
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
async batchInsert({ tableName, records }) {
|
|
1693
|
+
const transaction = this.pool.transaction();
|
|
1694
|
+
try {
|
|
1695
|
+
await transaction.begin();
|
|
1696
|
+
for (const record of records) {
|
|
1697
|
+
await this.insert({ tableName, record, transaction });
|
|
1698
|
+
}
|
|
1699
|
+
await transaction.commit();
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
await transaction.rollback();
|
|
1702
|
+
throw new MastraError(
|
|
1703
|
+
{
|
|
1704
|
+
id: createStorageErrorId("MSSQL", "BATCH_INSERT", "FAILED"),
|
|
1705
|
+
domain: ErrorDomain.STORAGE,
|
|
1706
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1707
|
+
details: {
|
|
1708
|
+
tableName,
|
|
1709
|
+
numberOfRecords: records.length
|
|
1710
|
+
}
|
|
1711
|
+
},
|
|
1712
|
+
error
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
async dropTable({ tableName }) {
|
|
1717
|
+
try {
|
|
1718
|
+
const tableNameWithSchema = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
|
|
1719
|
+
await this.pool.request().query(`DROP TABLE IF EXISTS ${tableNameWithSchema}`);
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
throw new MastraError(
|
|
1722
|
+
{
|
|
1723
|
+
id: createStorageErrorId("MSSQL", "DROP_TABLE", "FAILED"),
|
|
1724
|
+
domain: ErrorDomain.STORAGE,
|
|
1725
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1726
|
+
details: {
|
|
1727
|
+
tableName
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
error
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
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;
|
|
1770
|
+
}
|
|
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 sql2.NVarChar;
|
|
1779
|
+
case "timestamp":
|
|
1780
|
+
return sql2.DateTime2;
|
|
1781
|
+
case "uuid":
|
|
1782
|
+
return sql2.UniqueIdentifier;
|
|
1783
|
+
case "jsonb":
|
|
1784
|
+
return sql2.NVarChar;
|
|
1785
|
+
case "integer":
|
|
1786
|
+
return sql2.Int;
|
|
1787
|
+
case "bigint":
|
|
1788
|
+
return sql2.BigInt;
|
|
1789
|
+
case "float":
|
|
1790
|
+
return sql2.Float;
|
|
1791
|
+
case "boolean":
|
|
1792
|
+
return sql2.Bit;
|
|
1793
|
+
default:
|
|
1794
|
+
return sql2.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
|
+
});
|
|
576
2124
|
}
|
|
577
|
-
return
|
|
2125
|
+
return indexes;
|
|
578
2126
|
} catch (error) {
|
|
579
2127
|
throw new MastraError(
|
|
580
2128
|
{
|
|
581
|
-
id: "
|
|
2129
|
+
id: createStorageErrorId("MSSQL", "INDEX_LIST", "FAILED"),
|
|
582
2130
|
domain: ErrorDomain.STORAGE,
|
|
583
2131
|
category: ErrorCategory.THIRD_PARTY,
|
|
584
|
-
details: {
|
|
2132
|
+
details: tableName ? {
|
|
585
2133
|
tableName
|
|
586
|
-
}
|
|
2134
|
+
} : {}
|
|
587
2135
|
},
|
|
588
2136
|
error
|
|
589
2137
|
);
|
|
590
2138
|
}
|
|
591
2139
|
}
|
|
592
|
-
|
|
2140
|
+
/**
|
|
2141
|
+
* Get detailed statistics for a specific index
|
|
2142
|
+
*/
|
|
2143
|
+
async describeIndex(indexName) {
|
|
593
2144
|
try {
|
|
594
|
-
const
|
|
595
|
-
id,
|
|
596
|
-
[resourceId],
|
|
597
|
-
title,
|
|
598
|
-
metadata,
|
|
599
|
-
[createdAt],
|
|
600
|
-
[updatedAt]
|
|
601
|
-
FROM ${this.getTableName(TABLE_THREADS)}
|
|
602
|
-
WHERE id = @threadId`;
|
|
2145
|
+
const schemaName = this.schemaName || "dbo";
|
|
603
2146
|
const request = this.pool.request();
|
|
604
|
-
request.input("
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
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}"`);
|
|
609
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
|
+
`);
|
|
610
2187
|
return {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
|
615
2198
|
};
|
|
616
2199
|
} catch (error) {
|
|
617
2200
|
throw new MastraError(
|
|
618
2201
|
{
|
|
619
|
-
id: "
|
|
2202
|
+
id: createStorageErrorId("MSSQL", "INDEX_DESCRIBE", "FAILED"),
|
|
620
2203
|
domain: ErrorDomain.STORAGE,
|
|
621
2204
|
category: ErrorCategory.THIRD_PARTY,
|
|
622
2205
|
details: {
|
|
623
|
-
|
|
2206
|
+
indexName
|
|
624
2207
|
}
|
|
625
2208
|
},
|
|
626
2209
|
error
|
|
627
2210
|
);
|
|
628
2211
|
}
|
|
629
2212
|
}
|
|
630
|
-
|
|
631
|
-
|
|
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() {
|
|
632
2271
|
try {
|
|
633
|
-
const
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const total = parseInt(countResult.recordset[0]?.count ?? "0", 10);
|
|
641
|
-
if (total === 0) {
|
|
642
|
-
return {
|
|
643
|
-
threads: [],
|
|
644
|
-
total: 0,
|
|
645
|
-
page,
|
|
646
|
-
perPage,
|
|
647
|
-
hasMore: false
|
|
648
|
-
};
|
|
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
|
+
}
|
|
649
2279
|
}
|
|
650
|
-
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY [seq_id] DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
651
|
-
const dataRequest = this.pool.request();
|
|
652
|
-
dataRequest.input("resourceId", resourceId);
|
|
653
|
-
dataRequest.input("perPage", perPage);
|
|
654
|
-
dataRequest.input("offset", currentOffset);
|
|
655
|
-
const rowsResult = await dataRequest.query(dataQuery);
|
|
656
|
-
const rows = rowsResult.recordset || [];
|
|
657
|
-
const threads = rows.map((thread) => ({
|
|
658
|
-
...thread,
|
|
659
|
-
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
660
|
-
createdAt: thread.createdAt,
|
|
661
|
-
updatedAt: thread.updatedAt
|
|
662
|
-
}));
|
|
663
|
-
return {
|
|
664
|
-
threads,
|
|
665
|
-
total,
|
|
666
|
-
page,
|
|
667
|
-
perPage,
|
|
668
|
-
hasMore: currentOffset + threads.length < total
|
|
669
|
-
};
|
|
670
2280
|
} catch (error) {
|
|
671
|
-
|
|
2281
|
+
throw new MastraError(
|
|
672
2282
|
{
|
|
673
|
-
id: "
|
|
2283
|
+
id: createStorageErrorId("MSSQL", "CREATE_PERFORMANCE_INDEXES", "FAILED"),
|
|
674
2284
|
domain: ErrorDomain.STORAGE,
|
|
675
|
-
category: ErrorCategory.THIRD_PARTY
|
|
676
|
-
details: {
|
|
677
|
-
resourceId,
|
|
678
|
-
page
|
|
679
|
-
}
|
|
2285
|
+
category: ErrorCategory.THIRD_PARTY
|
|
680
2286
|
},
|
|
681
2287
|
error
|
|
682
2288
|
);
|
|
683
|
-
this.logger?.error?.(mastraError.toString());
|
|
684
|
-
this.logger?.trackException?.(mastraError);
|
|
685
|
-
return { threads: [], total: 0, page, perPage: perPageInput || 100, hasMore: false };
|
|
686
2289
|
}
|
|
687
2290
|
}
|
|
688
|
-
|
|
2291
|
+
};
|
|
2292
|
+
function transformScoreRow(row) {
|
|
2293
|
+
return transformScoreRow$1(row, {
|
|
2294
|
+
convertTimestamps: true
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
var ScoresMSSQL = class extends ScoresStorage {
|
|
2298
|
+
pool;
|
|
2299
|
+
operations;
|
|
2300
|
+
schema;
|
|
2301
|
+
constructor({
|
|
2302
|
+
pool,
|
|
2303
|
+
operations,
|
|
2304
|
+
schema
|
|
2305
|
+
}) {
|
|
2306
|
+
super();
|
|
2307
|
+
this.pool = pool;
|
|
2308
|
+
this.operations = operations;
|
|
2309
|
+
this.schema = schema;
|
|
2310
|
+
}
|
|
2311
|
+
async getScoreById({ id }) {
|
|
689
2312
|
try {
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
[createdAt] = @createdAt,
|
|
700
|
-
[updatedAt] = @updatedAt
|
|
701
|
-
WHEN NOT MATCHED THEN
|
|
702
|
-
INSERT (id, [resourceId], title, metadata, [createdAt], [updatedAt])
|
|
703
|
-
VALUES (@id, @resourceId, @title, @metadata, @createdAt, @updatedAt);`;
|
|
704
|
-
const req = this.pool.request();
|
|
705
|
-
req.input("id", thread.id);
|
|
706
|
-
req.input("resourceId", thread.resourceId);
|
|
707
|
-
req.input("title", thread.title);
|
|
708
|
-
req.input("metadata", thread.metadata ? JSON.stringify(thread.metadata) : null);
|
|
709
|
-
req.input("createdAt", thread.createdAt);
|
|
710
|
-
req.input("updatedAt", thread.updatedAt);
|
|
711
|
-
await req.query(mergeSql);
|
|
712
|
-
return thread;
|
|
2313
|
+
const request = this.pool.request();
|
|
2314
|
+
request.input("p1", id);
|
|
2315
|
+
const result = await request.query(
|
|
2316
|
+
`SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE id = @p1`
|
|
2317
|
+
);
|
|
2318
|
+
if (result.recordset.length === 0) {
|
|
2319
|
+
return null;
|
|
2320
|
+
}
|
|
2321
|
+
return transformScoreRow(result.recordset[0]);
|
|
713
2322
|
} catch (error) {
|
|
714
2323
|
throw new MastraError(
|
|
715
2324
|
{
|
|
716
|
-
id: "
|
|
2325
|
+
id: createStorageErrorId("MSSQL", "GET_SCORE_BY_ID", "FAILED"),
|
|
717
2326
|
domain: ErrorDomain.STORAGE,
|
|
718
2327
|
category: ErrorCategory.THIRD_PARTY,
|
|
719
|
-
details: {
|
|
720
|
-
threadId: thread.id
|
|
721
|
-
}
|
|
2328
|
+
details: { id }
|
|
722
2329
|
},
|
|
723
2330
|
error
|
|
724
2331
|
);
|
|
725
2332
|
}
|
|
726
2333
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
*/
|
|
730
|
-
async getThreadsByResourceId(args) {
|
|
731
|
-
const { resourceId } = args;
|
|
2334
|
+
async saveScore(score) {
|
|
2335
|
+
let validatedScore;
|
|
732
2336
|
try {
|
|
733
|
-
|
|
734
|
-
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY [seq_id] DESC`;
|
|
735
|
-
const request = this.pool.request();
|
|
736
|
-
request.input("resourceId", resourceId);
|
|
737
|
-
const resultSet = await request.query(dataQuery);
|
|
738
|
-
const rows = resultSet.recordset || [];
|
|
739
|
-
return rows.map((thread) => ({
|
|
740
|
-
...thread,
|
|
741
|
-
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
742
|
-
createdAt: thread.createdAt,
|
|
743
|
-
updatedAt: thread.updatedAt
|
|
744
|
-
}));
|
|
2337
|
+
validatedScore = saveScorePayloadSchema.parse(score);
|
|
745
2338
|
} catch (error) {
|
|
746
|
-
|
|
747
|
-
|
|
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
|
+
);
|
|
748
2354
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
2355
|
+
try {
|
|
2356
|
+
const scoreId = randomUUID();
|
|
2357
|
+
const now = /* @__PURE__ */ new Date();
|
|
2358
|
+
const {
|
|
2359
|
+
scorer,
|
|
2360
|
+
preprocessStepResult,
|
|
2361
|
+
analyzeStepResult,
|
|
2362
|
+
metadata,
|
|
2363
|
+
input,
|
|
2364
|
+
output,
|
|
2365
|
+
additionalContext,
|
|
2366
|
+
requestContext,
|
|
2367
|
+
entity,
|
|
2368
|
+
...rest
|
|
2369
|
+
} = validatedScore;
|
|
2370
|
+
await this.operations.insert({
|
|
2371
|
+
tableName: TABLE_SCORERS,
|
|
2372
|
+
record: {
|
|
2373
|
+
id: scoreId,
|
|
2374
|
+
...rest,
|
|
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()
|
|
768
2386
|
}
|
|
769
2387
|
});
|
|
2388
|
+
return { score: { ...validatedScore, id: scoreId, createdAt: now, updatedAt: now } };
|
|
2389
|
+
} catch (error) {
|
|
2390
|
+
throw new MastraError(
|
|
2391
|
+
{
|
|
2392
|
+
id: createStorageErrorId("MSSQL", "SAVE_SCORE", "FAILED"),
|
|
2393
|
+
domain: ErrorDomain.STORAGE,
|
|
2394
|
+
category: ErrorCategory.THIRD_PARTY
|
|
2395
|
+
},
|
|
2396
|
+
error
|
|
2397
|
+
);
|
|
770
2398
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
2399
|
+
}
|
|
2400
|
+
async listScoresByScorerId({
|
|
2401
|
+
scorerId,
|
|
2402
|
+
pagination,
|
|
2403
|
+
entityId,
|
|
2404
|
+
entityType,
|
|
2405
|
+
source
|
|
2406
|
+
}) {
|
|
775
2407
|
try {
|
|
776
|
-
const
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
const req = this.pool.request();
|
|
784
|
-
req.input("id", id);
|
|
785
|
-
req.input("title", title);
|
|
786
|
-
req.input("metadata", JSON.stringify(mergedMetadata));
|
|
787
|
-
req.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
|
|
788
|
-
const result = await req.query(sql2);
|
|
789
|
-
let thread = result.recordset && result.recordset[0];
|
|
790
|
-
if (thread && "seq_id" in thread) {
|
|
791
|
-
const { seq_id, ...rest } = thread;
|
|
792
|
-
thread = rest;
|
|
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++;
|
|
793
2415
|
}
|
|
794
|
-
if (
|
|
795
|
-
|
|
796
|
-
|
|
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}`);
|
|
2433
|
+
const total = totalResult.recordset[0]?.count || 0;
|
|
2434
|
+
const { page, perPage: perPageInput } = pagination;
|
|
2435
|
+
if (total === 0) {
|
|
2436
|
+
return {
|
|
2437
|
+
pagination: {
|
|
2438
|
+
total: 0,
|
|
2439
|
+
page,
|
|
2440
|
+
perPage: perPageInput,
|
|
2441
|
+
hasMore: false
|
|
2442
|
+
},
|
|
2443
|
+
scores: []
|
|
2444
|
+
};
|
|
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;
|
|
2450
|
+
const dataRequest = this.pool.request();
|
|
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);
|
|
2458
|
+
return {
|
|
2459
|
+
pagination: {
|
|
2460
|
+
total: Number(total),
|
|
2461
|
+
page,
|
|
2462
|
+
perPage: perPageForResponse,
|
|
2463
|
+
hasMore: end < total
|
|
2464
|
+
},
|
|
2465
|
+
scores: result.recordset.map((row) => transformScoreRow(row))
|
|
2466
|
+
};
|
|
2467
|
+
} catch (error) {
|
|
2468
|
+
throw new MastraError(
|
|
2469
|
+
{
|
|
2470
|
+
id: createStorageErrorId("MSSQL", "LIST_SCORES_BY_SCORER_ID", "FAILED"),
|
|
797
2471
|
domain: ErrorDomain.STORAGE,
|
|
798
|
-
category: ErrorCategory.
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
2472
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2473
|
+
details: { scorerId }
|
|
2474
|
+
},
|
|
2475
|
+
error
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
async listScoresByRunId({
|
|
2480
|
+
runId,
|
|
2481
|
+
pagination
|
|
2482
|
+
}) {
|
|
2483
|
+
try {
|
|
2484
|
+
const request = this.pool.request();
|
|
2485
|
+
request.input("p1", runId);
|
|
2486
|
+
const totalResult = await request.query(
|
|
2487
|
+
`SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1`
|
|
2488
|
+
);
|
|
2489
|
+
const total = totalResult.recordset[0]?.count || 0;
|
|
2490
|
+
const { page, perPage: perPageInput } = pagination;
|
|
2491
|
+
if (total === 0) {
|
|
2492
|
+
return {
|
|
2493
|
+
pagination: {
|
|
2494
|
+
total: 0,
|
|
2495
|
+
page,
|
|
2496
|
+
perPage: perPageInput,
|
|
2497
|
+
hasMore: false
|
|
2498
|
+
},
|
|
2499
|
+
scores: []
|
|
2500
|
+
};
|
|
805
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;
|
|
2506
|
+
const dataRequest = this.pool.request();
|
|
2507
|
+
dataRequest.input("p1", runId);
|
|
2508
|
+
dataRequest.input("p2", limitValue);
|
|
2509
|
+
dataRequest.input("p3", start);
|
|
2510
|
+
const result = await dataRequest.query(
|
|
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`
|
|
2512
|
+
);
|
|
806
2513
|
return {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
2514
|
+
pagination: {
|
|
2515
|
+
total: Number(total),
|
|
2516
|
+
page,
|
|
2517
|
+
perPage: perPageForResponse,
|
|
2518
|
+
hasMore: end < total
|
|
2519
|
+
},
|
|
2520
|
+
scores: result.recordset.map((row) => transformScoreRow(row))
|
|
811
2521
|
};
|
|
812
2522
|
} catch (error) {
|
|
813
2523
|
throw new MastraError(
|
|
814
2524
|
{
|
|
815
|
-
id: "
|
|
2525
|
+
id: createStorageErrorId("MSSQL", "LIST_SCORES_BY_RUN_ID", "FAILED"),
|
|
816
2526
|
domain: ErrorDomain.STORAGE,
|
|
817
2527
|
category: ErrorCategory.THIRD_PARTY,
|
|
818
|
-
details: {
|
|
819
|
-
threadId: id,
|
|
820
|
-
title
|
|
821
|
-
}
|
|
2528
|
+
details: { runId }
|
|
822
2529
|
},
|
|
823
2530
|
error
|
|
824
2531
|
);
|
|
825
2532
|
}
|
|
826
2533
|
}
|
|
827
|
-
async
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
const tx = this.pool.transaction();
|
|
2534
|
+
async listScoresByEntityId({
|
|
2535
|
+
entityId,
|
|
2536
|
+
entityType,
|
|
2537
|
+
pagination
|
|
2538
|
+
}) {
|
|
833
2539
|
try {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
await
|
|
838
|
-
|
|
839
|
-
|
|
2540
|
+
const request = this.pool.request();
|
|
2541
|
+
request.input("p1", entityId);
|
|
2542
|
+
request.input("p2", entityType);
|
|
2543
|
+
const totalResult = await request.query(
|
|
2544
|
+
`SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2`
|
|
2545
|
+
);
|
|
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);
|
|
2550
|
+
if (total === 0) {
|
|
2551
|
+
return {
|
|
2552
|
+
pagination: {
|
|
2553
|
+
total: 0,
|
|
2554
|
+
page,
|
|
2555
|
+
perPage: perPageForResponse,
|
|
2556
|
+
hasMore: false
|
|
2557
|
+
},
|
|
2558
|
+
scores: []
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2561
|
+
const limitValue = perPageInput === false ? total : perPage;
|
|
2562
|
+
const end = perPageInput === false ? total : start + perPage;
|
|
2563
|
+
const dataRequest = this.pool.request();
|
|
2564
|
+
dataRequest.input("p1", entityId);
|
|
2565
|
+
dataRequest.input("p2", entityType);
|
|
2566
|
+
dataRequest.input("p3", limitValue);
|
|
2567
|
+
dataRequest.input("p4", start);
|
|
2568
|
+
const result = await dataRequest.query(
|
|
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`
|
|
2570
|
+
);
|
|
2571
|
+
return {
|
|
2572
|
+
pagination: {
|
|
2573
|
+
total: Number(total),
|
|
2574
|
+
page,
|
|
2575
|
+
perPage: perPageForResponse,
|
|
2576
|
+
hasMore: end < total
|
|
2577
|
+
},
|
|
2578
|
+
scores: result.recordset.map((row) => transformScoreRow(row))
|
|
2579
|
+
};
|
|
840
2580
|
} catch (error) {
|
|
841
|
-
await tx.rollback().catch(() => {
|
|
842
|
-
});
|
|
843
2581
|
throw new MastraError(
|
|
844
2582
|
{
|
|
845
|
-
id: "
|
|
2583
|
+
id: createStorageErrorId("MSSQL", "LIST_SCORES_BY_ENTITY_ID", "FAILED"),
|
|
846
2584
|
domain: ErrorDomain.STORAGE,
|
|
847
2585
|
category: ErrorCategory.THIRD_PARTY,
|
|
848
|
-
details: {
|
|
849
|
-
threadId
|
|
850
|
-
}
|
|
2586
|
+
details: { entityId, entityType }
|
|
851
2587
|
},
|
|
852
2588
|
error
|
|
853
2589
|
);
|
|
854
2590
|
}
|
|
855
2591
|
}
|
|
856
|
-
async
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
2592
|
+
async listScoresBySpan({
|
|
2593
|
+
traceId,
|
|
2594
|
+
spanId,
|
|
2595
|
+
pagination
|
|
860
2596
|
}) {
|
|
861
|
-
const include = selectBy?.include;
|
|
862
|
-
if (!include) return null;
|
|
863
|
-
const unionQueries = [];
|
|
864
|
-
const paramValues = [];
|
|
865
|
-
let paramIdx = 1;
|
|
866
|
-
const paramNames = [];
|
|
867
|
-
for (const inc of include) {
|
|
868
|
-
const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
|
|
869
|
-
const searchId = inc.threadId || threadId;
|
|
870
|
-
const pThreadId = `@p${paramIdx}`;
|
|
871
|
-
const pId = `@p${paramIdx + 1}`;
|
|
872
|
-
const pPrev = `@p${paramIdx + 2}`;
|
|
873
|
-
const pNext = `@p${paramIdx + 3}`;
|
|
874
|
-
unionQueries.push(
|
|
875
|
-
`
|
|
876
|
-
SELECT
|
|
877
|
-
m.id,
|
|
878
|
-
m.content,
|
|
879
|
-
m.role,
|
|
880
|
-
m.type,
|
|
881
|
-
m.[createdAt],
|
|
882
|
-
m.thread_id AS threadId,
|
|
883
|
-
m.[resourceId],
|
|
884
|
-
m.seq_id
|
|
885
|
-
FROM (
|
|
886
|
-
SELECT *, ROW_NUMBER() OVER (${orderByStatement}) as row_num
|
|
887
|
-
FROM ${this.getTableName(TABLE_MESSAGES)}
|
|
888
|
-
WHERE [thread_id] = ${pThreadId}
|
|
889
|
-
) AS m
|
|
890
|
-
WHERE m.id = ${pId}
|
|
891
|
-
OR EXISTS (
|
|
892
|
-
SELECT 1
|
|
893
|
-
FROM (
|
|
894
|
-
SELECT *, ROW_NUMBER() OVER (${orderByStatement}) as row_num
|
|
895
|
-
FROM ${this.getTableName(TABLE_MESSAGES)}
|
|
896
|
-
WHERE [thread_id] = ${pThreadId}
|
|
897
|
-
) AS target
|
|
898
|
-
WHERE target.id = ${pId}
|
|
899
|
-
AND (
|
|
900
|
-
(m.row_num <= target.row_num + ${pPrev} AND m.row_num > target.row_num)
|
|
901
|
-
OR
|
|
902
|
-
(m.row_num >= target.row_num - ${pNext} AND m.row_num < target.row_num)
|
|
903
|
-
)
|
|
904
|
-
)
|
|
905
|
-
`
|
|
906
|
-
);
|
|
907
|
-
paramValues.push(searchId, id, withPreviousMessages, withNextMessages);
|
|
908
|
-
paramNames.push(`p${paramIdx}`, `p${paramIdx + 1}`, `p${paramIdx + 2}`, `p${paramIdx + 3}`);
|
|
909
|
-
paramIdx += 4;
|
|
910
|
-
}
|
|
911
|
-
const finalQuery = `
|
|
912
|
-
SELECT * FROM (
|
|
913
|
-
${unionQueries.join(" UNION ALL ")}
|
|
914
|
-
) AS union_result
|
|
915
|
-
ORDER BY [seq_id] ASC
|
|
916
|
-
`;
|
|
917
|
-
const req = this.pool.request();
|
|
918
|
-
for (let i = 0; i < paramValues.length; ++i) {
|
|
919
|
-
req.input(paramNames[i], paramValues[i]);
|
|
920
|
-
}
|
|
921
|
-
const result = await req.query(finalQuery);
|
|
922
|
-
const includedRows = result.recordset || [];
|
|
923
|
-
const seen = /* @__PURE__ */ new Set();
|
|
924
|
-
const dedupedRows = includedRows.filter((row) => {
|
|
925
|
-
if (seen.has(row.id)) return false;
|
|
926
|
-
seen.add(row.id);
|
|
927
|
-
return true;
|
|
928
|
-
});
|
|
929
|
-
return dedupedRows;
|
|
930
|
-
}
|
|
931
|
-
async getMessages(args) {
|
|
932
|
-
const { threadId, format, selectBy } = args;
|
|
933
|
-
const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId`;
|
|
934
|
-
const orderByStatement = `ORDER BY [seq_id] DESC`;
|
|
935
|
-
const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
|
|
936
2597
|
try {
|
|
937
|
-
let rows = [];
|
|
938
|
-
const include = selectBy?.include || [];
|
|
939
|
-
if (include?.length) {
|
|
940
|
-
const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
|
|
941
|
-
if (includeMessages) {
|
|
942
|
-
rows.push(...includeMessages);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
const excludeIds = rows.map((m) => m.id).filter(Boolean);
|
|
946
|
-
let query = `${selectStatement} FROM ${this.getTableName(TABLE_MESSAGES)} WHERE [thread_id] = @threadId`;
|
|
947
2598
|
const request = this.pool.request();
|
|
948
|
-
request.input("
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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
|
+
};
|
|
955
2618
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
message.content = message.content.parts;
|
|
976
|
-
} else {
|
|
977
|
-
message.content = [{ type: "text", text: "" }];
|
|
978
|
-
}
|
|
979
|
-
} else {
|
|
980
|
-
if (typeof message.content !== "object" || !message.content || !("parts" in message.content)) {
|
|
981
|
-
message.content = { format: 2, parts: [{ type: "text", text: "" }] };
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
if (message.type === "v2") delete message.type;
|
|
985
|
-
return message;
|
|
986
|
-
});
|
|
987
|
-
return format === "v2" ? fetchedMessages.map(
|
|
988
|
-
(m) => ({ ...m, content: m.content || { format: 2, parts: [{ type: "text", text: "" }] } })
|
|
989
|
-
) : fetchedMessages;
|
|
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
|
+
};
|
|
990
2638
|
} catch (error) {
|
|
991
|
-
|
|
2639
|
+
throw new MastraError(
|
|
992
2640
|
{
|
|
993
|
-
id: "
|
|
2641
|
+
id: createStorageErrorId("MSSQL", "LIST_SCORES_BY_SPAN", "FAILED"),
|
|
994
2642
|
domain: ErrorDomain.STORAGE,
|
|
995
2643
|
category: ErrorCategory.THIRD_PARTY,
|
|
996
|
-
details: {
|
|
997
|
-
threadId
|
|
998
|
-
}
|
|
2644
|
+
details: { traceId, spanId }
|
|
999
2645
|
},
|
|
1000
2646
|
error
|
|
1001
2647
|
);
|
|
1002
|
-
this.logger?.error?.(mastraError.toString());
|
|
1003
|
-
this.logger?.trackException(mastraError);
|
|
1004
|
-
return [];
|
|
1005
2648
|
}
|
|
1006
2649
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
2650
|
+
};
|
|
2651
|
+
var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
2652
|
+
pool;
|
|
2653
|
+
operations;
|
|
2654
|
+
schema;
|
|
2655
|
+
constructor({
|
|
2656
|
+
pool,
|
|
2657
|
+
operations,
|
|
2658
|
+
schema
|
|
2659
|
+
}) {
|
|
2660
|
+
super();
|
|
2661
|
+
this.pool = pool;
|
|
2662
|
+
this.operations = operations;
|
|
2663
|
+
this.schema = schema;
|
|
2664
|
+
}
|
|
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
|
+
}
|
|
1013
2673
|
}
|
|
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
|
+
};
|
|
2682
|
+
}
|
|
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();
|
|
1014
2692
|
try {
|
|
1015
|
-
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
request.input("toDate", toDate.toISOString());
|
|
1038
|
-
}
|
|
1039
|
-
const whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
1040
|
-
const countQuery = `SELECT COUNT(*) as total FROM ${this.getTableName(TABLE_MESSAGES)} ${whereClause}`;
|
|
1041
|
-
const countResult = await request.query(countQuery);
|
|
1042
|
-
const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
|
|
1043
|
-
if (total === 0 && messages2.length > 0) {
|
|
1044
|
-
const parsedIncluded = this._parseAndFormatMessages(messages2, format);
|
|
1045
|
-
return {
|
|
1046
|
-
messages: parsedIncluded,
|
|
1047
|
-
total: parsedIncluded.length,
|
|
1048
|
-
page: page2,
|
|
1049
|
-
perPage,
|
|
1050
|
-
hasMore: false
|
|
2693
|
+
await transaction.begin();
|
|
2694
|
+
const selectRequest = new sql2.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: {}
|
|
1051
2715
|
};
|
|
2716
|
+
} else {
|
|
2717
|
+
const existingSnapshot = existingSnapshotResult.recordset[0].snapshot;
|
|
2718
|
+
snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
|
|
1052
2719
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
page: page2,
|
|
1072
|
-
perPage,
|
|
1073
|
-
hasMore: currentOffset + rows.length < total
|
|
1074
|
-
};
|
|
2720
|
+
snapshot.context[stepId] = result;
|
|
2721
|
+
snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
|
|
2722
|
+
const upsertReq = new sql2.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", sql2.DateTime2, /* @__PURE__ */ new Date());
|
|
2727
|
+
upsertReq.input("updatedAt", sql2.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;
|
|
1075
2738
|
} catch (error) {
|
|
1076
|
-
|
|
2739
|
+
try {
|
|
2740
|
+
await transaction.rollback();
|
|
2741
|
+
} catch {
|
|
2742
|
+
}
|
|
2743
|
+
throw new MastraError(
|
|
1077
2744
|
{
|
|
1078
|
-
id: "
|
|
2745
|
+
id: createStorageErrorId("MSSQL", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
|
|
1079
2746
|
domain: ErrorDomain.STORAGE,
|
|
1080
2747
|
category: ErrorCategory.THIRD_PARTY,
|
|
1081
2748
|
details: {
|
|
1082
|
-
|
|
1083
|
-
|
|
2749
|
+
workflowName,
|
|
2750
|
+
runId,
|
|
2751
|
+
stepId
|
|
1084
2752
|
}
|
|
1085
2753
|
},
|
|
1086
2754
|
error
|
|
1087
2755
|
);
|
|
1088
|
-
this.logger?.error?.(mastraError.toString());
|
|
1089
|
-
this.logger?.trackException(mastraError);
|
|
1090
|
-
return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
|
|
1091
2756
|
}
|
|
1092
2757
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
try {
|
|
1098
|
-
parsed = { ...parsed, content: JSON.parse(parsed.content) };
|
|
1099
|
-
} catch {
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
if (format === "v1") {
|
|
1103
|
-
if (Array.isArray(parsed.content)) ; else if (parsed.content?.parts) {
|
|
1104
|
-
parsed.content = parsed.content.parts;
|
|
1105
|
-
} else {
|
|
1106
|
-
parsed.content = [{ type: "text", text: "" }];
|
|
1107
|
-
}
|
|
1108
|
-
} else {
|
|
1109
|
-
if (!parsed.content?.parts) {
|
|
1110
|
-
parsed = { ...parsed, content: { format: 2, parts: [{ type: "text", text: "" }] } };
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
return parsed;
|
|
1114
|
-
});
|
|
1115
|
-
const list = new MessageList().add(parsedMessages, "memory");
|
|
1116
|
-
return format === "v2" ? list.get.all.v2() : list.get.all.v1();
|
|
1117
|
-
}
|
|
1118
|
-
async saveMessages({
|
|
1119
|
-
messages,
|
|
1120
|
-
format
|
|
2758
|
+
async updateWorkflowState({
|
|
2759
|
+
workflowName,
|
|
2760
|
+
runId,
|
|
2761
|
+
opts
|
|
1121
2762
|
}) {
|
|
1122
|
-
|
|
1123
|
-
const
|
|
1124
|
-
if (!threadId) {
|
|
1125
|
-
throw new MastraError({
|
|
1126
|
-
id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
|
|
1127
|
-
domain: ErrorDomain.STORAGE,
|
|
1128
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
1129
|
-
text: `Thread ID is required`
|
|
1130
|
-
});
|
|
1131
|
-
}
|
|
1132
|
-
const thread = await this.getThreadById({ threadId });
|
|
1133
|
-
if (!thread) {
|
|
1134
|
-
throw new MastraError({
|
|
1135
|
-
id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
|
|
1136
|
-
domain: ErrorDomain.STORAGE,
|
|
1137
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
1138
|
-
text: `Thread ${threadId} not found`,
|
|
1139
|
-
details: { threadId }
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
|
-
const tableMessages = this.getTableName(TABLE_MESSAGES);
|
|
1143
|
-
const tableThreads = this.getTableName(TABLE_THREADS);
|
|
2763
|
+
const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
2764
|
+
const transaction = this.pool.transaction();
|
|
1144
2765
|
try {
|
|
1145
|
-
const transaction = this.pool.transaction();
|
|
1146
2766
|
await transaction.begin();
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
if (!message.resourceId) {
|
|
1155
|
-
throw new Error(
|
|
1156
|
-
`Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
|
|
1157
|
-
);
|
|
1158
|
-
}
|
|
1159
|
-
const request = transaction.request();
|
|
1160
|
-
request.input("id", message.id);
|
|
1161
|
-
request.input("thread_id", message.threadId);
|
|
1162
|
-
request.input(
|
|
1163
|
-
"content",
|
|
1164
|
-
typeof message.content === "string" ? message.content : JSON.stringify(message.content)
|
|
1165
|
-
);
|
|
1166
|
-
request.input("createdAt", message.createdAt.toISOString() || (/* @__PURE__ */ new Date()).toISOString());
|
|
1167
|
-
request.input("role", message.role);
|
|
1168
|
-
request.input("type", message.type || "v2");
|
|
1169
|
-
request.input("resourceId", message.resourceId);
|
|
1170
|
-
const mergeSql = `MERGE INTO ${tableMessages} AS target
|
|
1171
|
-
USING (SELECT @id AS id) AS src
|
|
1172
|
-
ON target.id = src.id
|
|
1173
|
-
WHEN MATCHED THEN UPDATE SET
|
|
1174
|
-
thread_id = @thread_id,
|
|
1175
|
-
content = @content,
|
|
1176
|
-
[createdAt] = @createdAt,
|
|
1177
|
-
role = @role,
|
|
1178
|
-
type = @type,
|
|
1179
|
-
resourceId = @resourceId
|
|
1180
|
-
WHEN NOT MATCHED THEN INSERT (id, thread_id, content, [createdAt], role, type, resourceId)
|
|
1181
|
-
VALUES (@id, @thread_id, @content, @createdAt, @role, @type, @resourceId);`;
|
|
1182
|
-
await request.query(mergeSql);
|
|
1183
|
-
}
|
|
1184
|
-
const threadReq = transaction.request();
|
|
1185
|
-
threadReq.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
|
|
1186
|
-
threadReq.input("id", threadId);
|
|
1187
|
-
await threadReq.query(`UPDATE ${tableThreads} SET [updatedAt] = @updatedAt WHERE id = @id`);
|
|
1188
|
-
await transaction.commit();
|
|
1189
|
-
} catch (error) {
|
|
2767
|
+
const selectRequest = new sql2.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) {
|
|
1190
2774
|
await transaction.rollback();
|
|
1191
|
-
|
|
2775
|
+
return void 0;
|
|
1192
2776
|
}
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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 sql2.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", sql2.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;
|
|
1206
2805
|
} catch (error) {
|
|
2806
|
+
try {
|
|
2807
|
+
await transaction.rollback();
|
|
2808
|
+
} catch {
|
|
2809
|
+
}
|
|
2810
|
+
if (error instanceof MastraError) throw error;
|
|
1207
2811
|
throw new MastraError(
|
|
1208
2812
|
{
|
|
1209
|
-
id: "
|
|
2813
|
+
id: createStorageErrorId("MSSQL", "UPDATE_WORKFLOW_STATE", "FAILED"),
|
|
1210
2814
|
domain: ErrorDomain.STORAGE,
|
|
1211
2815
|
category: ErrorCategory.THIRD_PARTY,
|
|
1212
|
-
details: {
|
|
2816
|
+
details: {
|
|
2817
|
+
workflowName,
|
|
2818
|
+
runId
|
|
2819
|
+
}
|
|
1213
2820
|
},
|
|
1214
2821
|
error
|
|
1215
2822
|
);
|
|
@@ -1218,30 +2825,33 @@ ${columns}
|
|
|
1218
2825
|
async persistWorkflowSnapshot({
|
|
1219
2826
|
workflowName,
|
|
1220
2827
|
runId,
|
|
2828
|
+
resourceId,
|
|
1221
2829
|
snapshot
|
|
1222
2830
|
}) {
|
|
1223
|
-
const table =
|
|
2831
|
+
const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
1224
2832
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1225
2833
|
try {
|
|
1226
2834
|
const request = this.pool.request();
|
|
1227
2835
|
request.input("workflow_name", workflowName);
|
|
1228
2836
|
request.input("run_id", runId);
|
|
2837
|
+
request.input("resourceId", resourceId);
|
|
1229
2838
|
request.input("snapshot", JSON.stringify(snapshot));
|
|
1230
|
-
request.input("createdAt", now);
|
|
1231
|
-
request.input("updatedAt", now);
|
|
2839
|
+
request.input("createdAt", sql2.DateTime2, new Date(now));
|
|
2840
|
+
request.input("updatedAt", sql2.DateTime2, new Date(now));
|
|
1232
2841
|
const mergeSql = `MERGE INTO ${table} AS target
|
|
1233
2842
|
USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
|
|
1234
2843
|
ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
|
|
1235
2844
|
WHEN MATCHED THEN UPDATE SET
|
|
2845
|
+
resourceId = @resourceId,
|
|
1236
2846
|
snapshot = @snapshot,
|
|
1237
2847
|
[updatedAt] = @updatedAt
|
|
1238
|
-
WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, snapshot, [createdAt], [updatedAt])
|
|
1239
|
-
VALUES (@workflow_name, @run_id, @snapshot, @createdAt, @updatedAt);`;
|
|
2848
|
+
WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, resourceId, snapshot, [createdAt], [updatedAt])
|
|
2849
|
+
VALUES (@workflow_name, @run_id, @resourceId, @snapshot, @createdAt, @updatedAt);`;
|
|
1240
2850
|
await request.query(mergeSql);
|
|
1241
2851
|
} catch (error) {
|
|
1242
2852
|
throw new MastraError(
|
|
1243
2853
|
{
|
|
1244
|
-
id: "
|
|
2854
|
+
id: createStorageErrorId("MSSQL", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
1245
2855
|
domain: ErrorDomain.STORAGE,
|
|
1246
2856
|
category: ErrorCategory.THIRD_PARTY,
|
|
1247
2857
|
details: {
|
|
@@ -1258,7 +2868,7 @@ ${columns}
|
|
|
1258
2868
|
runId
|
|
1259
2869
|
}) {
|
|
1260
2870
|
try {
|
|
1261
|
-
const result = await this.load({
|
|
2871
|
+
const result = await this.operations.load({
|
|
1262
2872
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
1263
2873
|
keys: {
|
|
1264
2874
|
workflow_name: workflowName,
|
|
@@ -1272,7 +2882,7 @@ ${columns}
|
|
|
1272
2882
|
} catch (error) {
|
|
1273
2883
|
throw new MastraError(
|
|
1274
2884
|
{
|
|
1275
|
-
id: "
|
|
2885
|
+
id: createStorageErrorId("MSSQL", "LOAD_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
1276
2886
|
domain: ErrorDomain.STORAGE,
|
|
1277
2887
|
category: ErrorCategory.THIRD_PARTY,
|
|
1278
2888
|
details: {
|
|
@@ -1284,107 +2894,6 @@ ${columns}
|
|
|
1284
2894
|
);
|
|
1285
2895
|
}
|
|
1286
2896
|
}
|
|
1287
|
-
async hasColumn(table, column) {
|
|
1288
|
-
const schema = this.schema || "dbo";
|
|
1289
|
-
const request = this.pool.request();
|
|
1290
|
-
request.input("schema", schema);
|
|
1291
|
-
request.input("table", table);
|
|
1292
|
-
request.input("column", column);
|
|
1293
|
-
request.input("columnLower", column.toLowerCase());
|
|
1294
|
-
const result = await request.query(
|
|
1295
|
-
`SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
|
|
1296
|
-
);
|
|
1297
|
-
return result.recordset.length > 0;
|
|
1298
|
-
}
|
|
1299
|
-
parseWorkflowRun(row) {
|
|
1300
|
-
let parsedSnapshot = row.snapshot;
|
|
1301
|
-
if (typeof parsedSnapshot === "string") {
|
|
1302
|
-
try {
|
|
1303
|
-
parsedSnapshot = JSON.parse(row.snapshot);
|
|
1304
|
-
} catch (e) {
|
|
1305
|
-
console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
return {
|
|
1309
|
-
workflowName: row.workflow_name,
|
|
1310
|
-
runId: row.run_id,
|
|
1311
|
-
snapshot: parsedSnapshot,
|
|
1312
|
-
createdAt: row.createdAt,
|
|
1313
|
-
updatedAt: row.updatedAt,
|
|
1314
|
-
resourceId: row.resourceId
|
|
1315
|
-
};
|
|
1316
|
-
}
|
|
1317
|
-
async getWorkflowRuns({
|
|
1318
|
-
workflowName,
|
|
1319
|
-
fromDate,
|
|
1320
|
-
toDate,
|
|
1321
|
-
limit,
|
|
1322
|
-
offset,
|
|
1323
|
-
resourceId
|
|
1324
|
-
} = {}) {
|
|
1325
|
-
try {
|
|
1326
|
-
const conditions = [];
|
|
1327
|
-
const paramMap = {};
|
|
1328
|
-
if (workflowName) {
|
|
1329
|
-
conditions.push(`[workflow_name] = @workflowName`);
|
|
1330
|
-
paramMap["workflowName"] = workflowName;
|
|
1331
|
-
}
|
|
1332
|
-
if (resourceId) {
|
|
1333
|
-
const hasResourceId = await this.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
|
|
1334
|
-
if (hasResourceId) {
|
|
1335
|
-
conditions.push(`[resourceId] = @resourceId`);
|
|
1336
|
-
paramMap["resourceId"] = resourceId;
|
|
1337
|
-
} else {
|
|
1338
|
-
console.warn(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
|
|
1342
|
-
conditions.push(`[createdAt] >= @fromDate`);
|
|
1343
|
-
paramMap[`fromDate`] = fromDate.toISOString();
|
|
1344
|
-
}
|
|
1345
|
-
if (toDate instanceof Date && !isNaN(toDate.getTime())) {
|
|
1346
|
-
conditions.push(`[createdAt] <= @toDate`);
|
|
1347
|
-
paramMap[`toDate`] = toDate.toISOString();
|
|
1348
|
-
}
|
|
1349
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1350
|
-
let total = 0;
|
|
1351
|
-
const tableName = this.getTableName(TABLE_WORKFLOW_SNAPSHOT);
|
|
1352
|
-
const request = this.pool.request();
|
|
1353
|
-
Object.entries(paramMap).forEach(([key, value]) => {
|
|
1354
|
-
if (value instanceof Date) {
|
|
1355
|
-
request.input(key, sql.DateTime, value);
|
|
1356
|
-
} else {
|
|
1357
|
-
request.input(key, value);
|
|
1358
|
-
}
|
|
1359
|
-
});
|
|
1360
|
-
if (limit !== void 0 && offset !== void 0) {
|
|
1361
|
-
const countQuery = `SELECT COUNT(*) as count FROM ${tableName} ${whereClause}`;
|
|
1362
|
-
const countResult = await request.query(countQuery);
|
|
1363
|
-
total = Number(countResult.recordset[0]?.count || 0);
|
|
1364
|
-
}
|
|
1365
|
-
let query = `SELECT * FROM ${tableName} ${whereClause} ORDER BY [seq_id] DESC`;
|
|
1366
|
-
if (limit !== void 0 && offset !== void 0) {
|
|
1367
|
-
query += ` OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
|
|
1368
|
-
request.input("limit", limit);
|
|
1369
|
-
request.input("offset", offset);
|
|
1370
|
-
}
|
|
1371
|
-
const result = await request.query(query);
|
|
1372
|
-
const runs = (result.recordset || []).map((row) => this.parseWorkflowRun(row));
|
|
1373
|
-
return { runs, total: total || runs.length };
|
|
1374
|
-
} catch (error) {
|
|
1375
|
-
throw new MastraError(
|
|
1376
|
-
{
|
|
1377
|
-
id: "MASTRA_STORAGE_MSSQL_STORE_GET_WORKFLOW_RUNS_FAILED",
|
|
1378
|
-
domain: ErrorDomain.STORAGE,
|
|
1379
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
1380
|
-
details: {
|
|
1381
|
-
workflowName: workflowName || "all"
|
|
1382
|
-
}
|
|
1383
|
-
},
|
|
1384
|
-
error
|
|
1385
|
-
);
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
2897
|
async getWorkflowRunById({
|
|
1389
2898
|
runId,
|
|
1390
2899
|
workflowName
|
|
@@ -1401,7 +2910,7 @@ ${columns}
|
|
|
1401
2910
|
paramMap["workflowName"] = workflowName;
|
|
1402
2911
|
}
|
|
1403
2912
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1404
|
-
const tableName =
|
|
2913
|
+
const tableName = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
1405
2914
|
const query = `SELECT * FROM ${tableName} ${whereClause}`;
|
|
1406
2915
|
const request = this.pool.request();
|
|
1407
2916
|
Object.entries(paramMap).forEach(([key, value]) => request.input(key, value));
|
|
@@ -1413,7 +2922,7 @@ ${columns}
|
|
|
1413
2922
|
} catch (error) {
|
|
1414
2923
|
throw new MastraError(
|
|
1415
2924
|
{
|
|
1416
|
-
id: "
|
|
2925
|
+
id: createStorageErrorId("MSSQL", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
1417
2926
|
domain: ErrorDomain.STORAGE,
|
|
1418
2927
|
category: ErrorCategory.THIRD_PARTY,
|
|
1419
2928
|
details: {
|
|
@@ -1425,390 +2934,407 @@ ${columns}
|
|
|
1425
2934
|
);
|
|
1426
2935
|
}
|
|
1427
2936
|
}
|
|
1428
|
-
async
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
if (idParams.length > 0) {
|
|
1438
|
-
selectQuery += ` WHERE id IN (${idParams})`;
|
|
1439
|
-
} else {
|
|
1440
|
-
return [];
|
|
1441
|
-
}
|
|
1442
|
-
const selectReq = this.pool.request();
|
|
1443
|
-
messageIds.forEach((id, i) => selectReq.input(`id${i}`, id));
|
|
1444
|
-
const existingMessagesDb = (await selectReq.query(selectQuery)).recordset;
|
|
1445
|
-
if (!existingMessagesDb || existingMessagesDb.length === 0) {
|
|
1446
|
-
return [];
|
|
1447
|
-
}
|
|
1448
|
-
const existingMessages = existingMessagesDb.map((msg) => {
|
|
1449
|
-
if (typeof msg.content === "string") {
|
|
1450
|
-
try {
|
|
1451
|
-
msg.content = JSON.parse(msg.content);
|
|
1452
|
-
} catch {
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
return msg;
|
|
1456
|
-
});
|
|
1457
|
-
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
1458
|
-
const transaction = this.pool.transaction();
|
|
2937
|
+
async listWorkflowRuns({
|
|
2938
|
+
workflowName,
|
|
2939
|
+
fromDate,
|
|
2940
|
+
toDate,
|
|
2941
|
+
page,
|
|
2942
|
+
perPage,
|
|
2943
|
+
resourceId,
|
|
2944
|
+
status
|
|
2945
|
+
} = {}) {
|
|
1459
2946
|
try {
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
if (Object.keys(fieldsToUpdate).length === 0) continue;
|
|
1466
|
-
threadIdsToUpdate.add(existingMessage.threadId);
|
|
1467
|
-
if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
|
|
1468
|
-
threadIdsToUpdate.add(updatePayload.threadId);
|
|
1469
|
-
}
|
|
1470
|
-
const setClauses = [];
|
|
1471
|
-
const req = transaction.request();
|
|
1472
|
-
req.input("id", id);
|
|
1473
|
-
const columnMapping = { threadId: "thread_id" };
|
|
1474
|
-
const updatableFields = { ...fieldsToUpdate };
|
|
1475
|
-
if (updatableFields.content) {
|
|
1476
|
-
const newContent = {
|
|
1477
|
-
...existingMessage.content,
|
|
1478
|
-
...updatableFields.content,
|
|
1479
|
-
...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: { ...existingMessage.content.metadata, ...updatableFields.content.metadata } } : {}
|
|
1480
|
-
};
|
|
1481
|
-
setClauses.push(`content = @content`);
|
|
1482
|
-
req.input("content", JSON.stringify(newContent));
|
|
1483
|
-
delete updatableFields.content;
|
|
1484
|
-
}
|
|
1485
|
-
for (const key in updatableFields) {
|
|
1486
|
-
if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
|
|
1487
|
-
const dbColumn = columnMapping[key] || key;
|
|
1488
|
-
setClauses.push(`[${dbColumn}] = @${dbColumn}`);
|
|
1489
|
-
req.input(dbColumn, updatableFields[key]);
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
if (setClauses.length > 0) {
|
|
1493
|
-
const updateSql = `UPDATE ${this.getTableName(TABLE_MESSAGES)} SET ${setClauses.join(", ")} WHERE id = @id`;
|
|
1494
|
-
await req.query(updateSql);
|
|
1495
|
-
}
|
|
2947
|
+
const conditions = [];
|
|
2948
|
+
const paramMap = {};
|
|
2949
|
+
if (workflowName) {
|
|
2950
|
+
conditions.push(`[workflow_name] = @workflowName`);
|
|
2951
|
+
paramMap["workflowName"] = workflowName;
|
|
1496
2952
|
}
|
|
1497
|
-
if (
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
Array.from(threadIdsToUpdate).forEach((tid, i) => threadReq.input(`tid${i}`, tid));
|
|
1501
|
-
threadReq.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
|
|
1502
|
-
const threadSql = `UPDATE ${this.getTableName(TABLE_THREADS)} SET updatedAt = @updatedAt WHERE id IN (${threadIdParams})`;
|
|
1503
|
-
await threadReq.query(threadSql);
|
|
2953
|
+
if (status) {
|
|
2954
|
+
conditions.push(`JSON_VALUE([snapshot], '$.status') = @status`);
|
|
2955
|
+
paramMap["status"] = status;
|
|
1504
2956
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
category: ErrorCategory.THIRD_PARTY
|
|
1513
|
-
},
|
|
1514
|
-
error
|
|
1515
|
-
);
|
|
1516
|
-
}
|
|
1517
|
-
const refetchReq = this.pool.request();
|
|
1518
|
-
messageIds.forEach((id, i) => refetchReq.input(`id${i}`, id));
|
|
1519
|
-
const updatedMessages = (await refetchReq.query(selectQuery)).recordset;
|
|
1520
|
-
return (updatedMessages || []).map((message) => {
|
|
1521
|
-
if (typeof message.content === "string") {
|
|
1522
|
-
try {
|
|
1523
|
-
message.content = JSON.parse(message.content);
|
|
1524
|
-
} catch {
|
|
2957
|
+
if (resourceId) {
|
|
2958
|
+
const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
|
|
2959
|
+
if (hasResourceId) {
|
|
2960
|
+
conditions.push(`[resourceId] = @resourceId`);
|
|
2961
|
+
paramMap["resourceId"] = resourceId;
|
|
2962
|
+
} else {
|
|
2963
|
+
this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
1525
2964
|
}
|
|
1526
2965
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
async close() {
|
|
1531
|
-
if (this.pool) {
|
|
1532
|
-
try {
|
|
1533
|
-
if (this.pool.connected) {
|
|
1534
|
-
await this.pool.close();
|
|
1535
|
-
} else if (this.pool.connecting) {
|
|
1536
|
-
await this.pool.connect();
|
|
1537
|
-
await this.pool.close();
|
|
1538
|
-
}
|
|
1539
|
-
} catch (err) {
|
|
1540
|
-
if (err.message && err.message.includes("Cannot close a pool while it is connecting")) ; else {
|
|
1541
|
-
throw err;
|
|
1542
|
-
}
|
|
2966
|
+
if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
|
|
2967
|
+
conditions.push(`[createdAt] >= @fromDate`);
|
|
2968
|
+
paramMap[`fromDate`] = fromDate.toISOString();
|
|
1543
2969
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
const { agentName, type, page = 0, perPage = 100, dateRange } = options;
|
|
1548
|
-
const fromDate = dateRange?.start;
|
|
1549
|
-
const toDate = dateRange?.end;
|
|
1550
|
-
const where = [];
|
|
1551
|
-
const params = {};
|
|
1552
|
-
if (agentName) {
|
|
1553
|
-
where.push("agent_name = @agentName");
|
|
1554
|
-
params["agentName"] = agentName;
|
|
1555
|
-
}
|
|
1556
|
-
if (type === "test") {
|
|
1557
|
-
where.push("test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL");
|
|
1558
|
-
} else if (type === "live") {
|
|
1559
|
-
where.push("(test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)");
|
|
1560
|
-
}
|
|
1561
|
-
if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
|
|
1562
|
-
where.push(`[created_at] >= @fromDate`);
|
|
1563
|
-
params[`fromDate`] = fromDate.toISOString();
|
|
1564
|
-
}
|
|
1565
|
-
if (toDate instanceof Date && !isNaN(toDate.getTime())) {
|
|
1566
|
-
where.push(`[created_at] <= @toDate`);
|
|
1567
|
-
params[`toDate`] = toDate.toISOString();
|
|
1568
|
-
}
|
|
1569
|
-
const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
1570
|
-
const tableName = this.getTableName(TABLE_EVALS);
|
|
1571
|
-
const offset = page * perPage;
|
|
1572
|
-
const countQuery = `SELECT COUNT(*) as total FROM ${tableName} ${whereClause}`;
|
|
1573
|
-
const dataQuery = `SELECT * FROM ${tableName} ${whereClause} ORDER BY seq_id DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
1574
|
-
try {
|
|
1575
|
-
const countReq = this.pool.request();
|
|
1576
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
1577
|
-
if (value instanceof Date) {
|
|
1578
|
-
countReq.input(key, sql.DateTime, value);
|
|
1579
|
-
} else {
|
|
1580
|
-
countReq.input(key, value);
|
|
1581
|
-
}
|
|
1582
|
-
});
|
|
1583
|
-
const countResult = await countReq.query(countQuery);
|
|
1584
|
-
const total = countResult.recordset[0]?.total || 0;
|
|
1585
|
-
if (total === 0) {
|
|
1586
|
-
return {
|
|
1587
|
-
evals: [],
|
|
1588
|
-
total: 0,
|
|
1589
|
-
page,
|
|
1590
|
-
perPage,
|
|
1591
|
-
hasMore: false
|
|
1592
|
-
};
|
|
2970
|
+
if (toDate instanceof Date && !isNaN(toDate.getTime())) {
|
|
2971
|
+
conditions.push(`[createdAt] <= @toDate`);
|
|
2972
|
+
paramMap[`toDate`] = toDate.toISOString();
|
|
1593
2973
|
}
|
|
1594
|
-
const
|
|
1595
|
-
|
|
2974
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2975
|
+
let total = 0;
|
|
2976
|
+
const tableName = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
|
|
2977
|
+
const request = this.pool.request();
|
|
2978
|
+
Object.entries(paramMap).forEach(([key, value]) => {
|
|
1596
2979
|
if (value instanceof Date) {
|
|
1597
|
-
|
|
2980
|
+
request.input(key, sql2.DateTime, value);
|
|
1598
2981
|
} else {
|
|
1599
|
-
|
|
2982
|
+
request.input(key, value);
|
|
1600
2983
|
}
|
|
1601
2984
|
});
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
perPage,
|
|
1611
|
-
|
|
1612
|
-
|
|
2985
|
+
const usePagination = typeof perPage === "number" && typeof page === "number";
|
|
2986
|
+
if (usePagination) {
|
|
2987
|
+
const countQuery = `SELECT COUNT(*) as count FROM ${tableName} ${whereClause}`;
|
|
2988
|
+
const countResult = await request.query(countQuery);
|
|
2989
|
+
total = Number(countResult.recordset[0]?.count || 0);
|
|
2990
|
+
}
|
|
2991
|
+
let query = `SELECT * FROM ${tableName} ${whereClause} ORDER BY [seq_id] DESC`;
|
|
2992
|
+
if (usePagination) {
|
|
2993
|
+
const normalizedPerPage = normalizePerPage(perPage, Number.MAX_SAFE_INTEGER);
|
|
2994
|
+
const offset = page * normalizedPerPage;
|
|
2995
|
+
query += ` OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
2996
|
+
request.input("perPage", normalizedPerPage);
|
|
2997
|
+
request.input("offset", offset);
|
|
2998
|
+
}
|
|
2999
|
+
const result = await request.query(query);
|
|
3000
|
+
const runs = (result.recordset || []).map((row) => this.parseWorkflowRun(row));
|
|
3001
|
+
return { runs, total: total || runs.length };
|
|
1613
3002
|
} catch (error) {
|
|
1614
|
-
|
|
3003
|
+
throw new MastraError(
|
|
1615
3004
|
{
|
|
1616
|
-
id: "
|
|
3005
|
+
id: createStorageErrorId("MSSQL", "LIST_WORKFLOW_RUNS", "FAILED"),
|
|
1617
3006
|
domain: ErrorDomain.STORAGE,
|
|
1618
3007
|
category: ErrorCategory.THIRD_PARTY,
|
|
1619
3008
|
details: {
|
|
1620
|
-
|
|
1621
|
-
type: type || "all",
|
|
1622
|
-
page,
|
|
1623
|
-
perPage
|
|
3009
|
+
workflowName: workflowName || "all"
|
|
1624
3010
|
}
|
|
1625
3011
|
},
|
|
1626
3012
|
error
|
|
1627
3013
|
);
|
|
1628
|
-
this.logger?.error?.(mastraError.toString());
|
|
1629
|
-
this.logger?.trackException(mastraError);
|
|
1630
|
-
throw mastraError;
|
|
1631
3014
|
}
|
|
1632
3015
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
);
|
|
1645
|
-
return resource;
|
|
1646
|
-
} catch (error) {
|
|
1647
|
-
const mastraError = new MastraError(
|
|
1648
|
-
{
|
|
1649
|
-
id: "MASTRA_STORAGE_MSSQL_SAVE_RESOURCE_FAILED",
|
|
1650
|
-
domain: ErrorDomain.STORAGE,
|
|
1651
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
1652
|
-
details: { resourceId: resource.id }
|
|
1653
|
-
},
|
|
1654
|
-
error
|
|
1655
|
-
);
|
|
1656
|
-
this.logger?.error?.(mastraError.toString());
|
|
1657
|
-
this.logger?.trackException(mastraError);
|
|
1658
|
-
throw mastraError;
|
|
3016
|
+
};
|
|
3017
|
+
|
|
3018
|
+
// src/storage/index.ts
|
|
3019
|
+
var MSSQLStore = class extends MastraStorage {
|
|
3020
|
+
pool;
|
|
3021
|
+
schema;
|
|
3022
|
+
isConnected = null;
|
|
3023
|
+
stores;
|
|
3024
|
+
constructor(config) {
|
|
3025
|
+
if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
|
|
3026
|
+
throw new Error("MSSQLStore: id must be provided and cannot be empty.");
|
|
1659
3027
|
}
|
|
1660
|
-
|
|
1661
|
-
async updateResource({
|
|
1662
|
-
resourceId,
|
|
1663
|
-
workingMemory,
|
|
1664
|
-
metadata
|
|
1665
|
-
}) {
|
|
3028
|
+
super({ id: config.id, name: "MSSQLStore", disableInit: config.disableInit });
|
|
1666
3029
|
try {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
3030
|
+
if ("connectionString" in config) {
|
|
3031
|
+
if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
|
|
3032
|
+
throw new Error("MSSQLStore: connectionString must be provided and cannot be empty.");
|
|
3033
|
+
}
|
|
3034
|
+
} else {
|
|
3035
|
+
const required = ["server", "database", "user", "password"];
|
|
3036
|
+
for (const key of required) {
|
|
3037
|
+
if (!(key in config) || typeof config[key] !== "string" || config[key].trim() === "") {
|
|
3038
|
+
throw new Error(`MSSQLStore: ${key} must be provided and cannot be empty.`);
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
1677
3041
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
3042
|
+
this.schema = config.schemaName || "dbo";
|
|
3043
|
+
this.pool = "connectionString" in config ? new sql2.ConnectionPool(config.connectionString) : new sql2.ConnectionPool({
|
|
3044
|
+
server: config.server,
|
|
3045
|
+
database: config.database,
|
|
3046
|
+
user: config.user,
|
|
3047
|
+
password: config.password,
|
|
3048
|
+
port: config.port,
|
|
3049
|
+
options: config.options || { encrypt: true, trustServerCertificate: true }
|
|
3050
|
+
});
|
|
3051
|
+
const operations = new StoreOperationsMSSQL({ pool: this.pool, schemaName: this.schema });
|
|
3052
|
+
const scores = new ScoresMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
3053
|
+
const workflows = new WorkflowsMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
3054
|
+
const memory = new MemoryMSSQL({ pool: this.pool, schema: this.schema, operations });
|
|
3055
|
+
const observability = new ObservabilityMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
3056
|
+
this.stores = {
|
|
3057
|
+
operations,
|
|
3058
|
+
scores,
|
|
3059
|
+
workflows,
|
|
3060
|
+
memory,
|
|
3061
|
+
observability
|
|
1686
3062
|
};
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
const req = this.pool.request();
|
|
1690
|
-
if (workingMemory !== void 0) {
|
|
1691
|
-
updates.push("workingMemory = @workingMemory");
|
|
1692
|
-
req.input("workingMemory", workingMemory);
|
|
1693
|
-
}
|
|
1694
|
-
if (metadata) {
|
|
1695
|
-
updates.push("metadata = @metadata");
|
|
1696
|
-
req.input("metadata", JSON.stringify(updatedResource.metadata));
|
|
1697
|
-
}
|
|
1698
|
-
updates.push("updatedAt = @updatedAt");
|
|
1699
|
-
req.input("updatedAt", updatedResource.updatedAt.toISOString());
|
|
1700
|
-
req.input("id", resourceId);
|
|
1701
|
-
await req.query(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = @id`);
|
|
1702
|
-
return updatedResource;
|
|
1703
|
-
} catch (error) {
|
|
1704
|
-
const mastraError = new MastraError(
|
|
3063
|
+
} catch (e) {
|
|
3064
|
+
throw new MastraError(
|
|
1705
3065
|
{
|
|
1706
|
-
id: "
|
|
3066
|
+
id: createStorageErrorId("MSSQL", "INITIALIZATION", "FAILED"),
|
|
1707
3067
|
domain: ErrorDomain.STORAGE,
|
|
1708
|
-
category: ErrorCategory.
|
|
1709
|
-
details: { resourceId }
|
|
3068
|
+
category: ErrorCategory.USER
|
|
1710
3069
|
},
|
|
1711
|
-
|
|
3070
|
+
e
|
|
1712
3071
|
);
|
|
1713
|
-
this.logger?.error?.(mastraError.toString());
|
|
1714
|
-
this.logger?.trackException(mastraError);
|
|
1715
|
-
throw mastraError;
|
|
1716
3072
|
}
|
|
1717
3073
|
}
|
|
1718
|
-
async
|
|
1719
|
-
|
|
3074
|
+
async init() {
|
|
3075
|
+
if (this.isConnected === null) {
|
|
3076
|
+
this.isConnected = this._performInitializationAndStore();
|
|
3077
|
+
}
|
|
1720
3078
|
try {
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
3079
|
+
await this.isConnected;
|
|
3080
|
+
await super.init();
|
|
3081
|
+
try {
|
|
3082
|
+
await this.stores.operations.createAutomaticIndexes();
|
|
3083
|
+
} catch (indexError) {
|
|
3084
|
+
this.logger?.warn?.("Failed to create indexes:", indexError);
|
|
1726
3085
|
}
|
|
1727
|
-
return {
|
|
1728
|
-
...result,
|
|
1729
|
-
workingMemory: typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
|
|
1730
|
-
metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
|
|
1731
|
-
};
|
|
1732
3086
|
} catch (error) {
|
|
1733
|
-
|
|
3087
|
+
this.isConnected = null;
|
|
3088
|
+
throw new MastraError(
|
|
1734
3089
|
{
|
|
1735
|
-
id: "
|
|
3090
|
+
id: createStorageErrorId("MSSQL", "INIT", "FAILED"),
|
|
1736
3091
|
domain: ErrorDomain.STORAGE,
|
|
1737
|
-
category: ErrorCategory.THIRD_PARTY
|
|
1738
|
-
details: { resourceId }
|
|
3092
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1739
3093
|
},
|
|
1740
3094
|
error
|
|
1741
3095
|
);
|
|
1742
|
-
this.logger?.error?.(mastraError.toString());
|
|
1743
|
-
this.logger?.trackException(mastraError);
|
|
1744
|
-
throw mastraError;
|
|
1745
3096
|
}
|
|
1746
3097
|
}
|
|
1747
|
-
async
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
});
|
|
3098
|
+
async _performInitializationAndStore() {
|
|
3099
|
+
try {
|
|
3100
|
+
await this.pool.connect();
|
|
3101
|
+
return true;
|
|
3102
|
+
} catch (err) {
|
|
3103
|
+
throw err;
|
|
3104
|
+
}
|
|
1755
3105
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
3106
|
+
get supports() {
|
|
3107
|
+
return {
|
|
3108
|
+
selectByIncludeResourceScope: true,
|
|
3109
|
+
resourceWorkingMemory: true,
|
|
3110
|
+
hasColumn: true,
|
|
3111
|
+
createTable: true,
|
|
3112
|
+
deleteMessages: true,
|
|
3113
|
+
listScoresBySpan: true,
|
|
3114
|
+
observabilityInstance: true,
|
|
3115
|
+
indexManagement: true
|
|
3116
|
+
};
|
|
1764
3117
|
}
|
|
1765
|
-
async
|
|
1766
|
-
|
|
3118
|
+
async createTable({
|
|
3119
|
+
tableName,
|
|
3120
|
+
schema
|
|
3121
|
+
}) {
|
|
3122
|
+
return this.stores.operations.createTable({ tableName, schema });
|
|
3123
|
+
}
|
|
3124
|
+
async alterTable({
|
|
3125
|
+
tableName,
|
|
3126
|
+
schema,
|
|
3127
|
+
ifNotExists
|
|
3128
|
+
}) {
|
|
3129
|
+
return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
|
|
3130
|
+
}
|
|
3131
|
+
async clearTable({ tableName }) {
|
|
3132
|
+
return this.stores.operations.clearTable({ tableName });
|
|
3133
|
+
}
|
|
3134
|
+
async dropTable({ tableName }) {
|
|
3135
|
+
return this.stores.operations.dropTable({ tableName });
|
|
3136
|
+
}
|
|
3137
|
+
async insert({ tableName, record }) {
|
|
3138
|
+
return this.stores.operations.insert({ tableName, record });
|
|
3139
|
+
}
|
|
3140
|
+
async batchInsert({ tableName, records }) {
|
|
3141
|
+
return this.stores.operations.batchInsert({ tableName, records });
|
|
3142
|
+
}
|
|
3143
|
+
async load({ tableName, keys }) {
|
|
3144
|
+
return this.stores.operations.load({ tableName, keys });
|
|
3145
|
+
}
|
|
3146
|
+
/**
|
|
3147
|
+
* Memory
|
|
3148
|
+
*/
|
|
3149
|
+
async getThreadById({ threadId }) {
|
|
3150
|
+
return this.stores.memory.getThreadById({ threadId });
|
|
3151
|
+
}
|
|
3152
|
+
async saveThread({ thread }) {
|
|
3153
|
+
return this.stores.memory.saveThread({ thread });
|
|
3154
|
+
}
|
|
3155
|
+
async updateThread({
|
|
3156
|
+
id,
|
|
3157
|
+
title,
|
|
3158
|
+
metadata
|
|
3159
|
+
}) {
|
|
3160
|
+
return this.stores.memory.updateThread({ id, title, metadata });
|
|
3161
|
+
}
|
|
3162
|
+
async deleteThread({ threadId }) {
|
|
3163
|
+
return this.stores.memory.deleteThread({ threadId });
|
|
3164
|
+
}
|
|
3165
|
+
async listMessagesById({ messageIds }) {
|
|
3166
|
+
return this.stores.memory.listMessagesById({ messageIds });
|
|
3167
|
+
}
|
|
3168
|
+
async saveMessages(args) {
|
|
3169
|
+
return this.stores.memory.saveMessages(args);
|
|
3170
|
+
}
|
|
3171
|
+
async updateMessages({
|
|
3172
|
+
messages
|
|
3173
|
+
}) {
|
|
3174
|
+
return this.stores.memory.updateMessages({ messages });
|
|
3175
|
+
}
|
|
3176
|
+
async deleteMessages(messageIds) {
|
|
3177
|
+
return this.stores.memory.deleteMessages(messageIds);
|
|
3178
|
+
}
|
|
3179
|
+
async getResourceById({ resourceId }) {
|
|
3180
|
+
return this.stores.memory.getResourceById({ resourceId });
|
|
3181
|
+
}
|
|
3182
|
+
async saveResource({ resource }) {
|
|
3183
|
+
return this.stores.memory.saveResource({ resource });
|
|
3184
|
+
}
|
|
3185
|
+
async updateResource({
|
|
3186
|
+
resourceId,
|
|
3187
|
+
workingMemory,
|
|
3188
|
+
metadata
|
|
3189
|
+
}) {
|
|
3190
|
+
return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
|
|
3191
|
+
}
|
|
3192
|
+
/**
|
|
3193
|
+
* Workflows
|
|
3194
|
+
*/
|
|
3195
|
+
async updateWorkflowResults({
|
|
3196
|
+
workflowName,
|
|
3197
|
+
runId,
|
|
3198
|
+
stepId,
|
|
3199
|
+
result,
|
|
3200
|
+
requestContext
|
|
3201
|
+
}) {
|
|
3202
|
+
return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
|
|
3203
|
+
}
|
|
3204
|
+
async updateWorkflowState({
|
|
3205
|
+
workflowName,
|
|
3206
|
+
runId,
|
|
3207
|
+
opts
|
|
3208
|
+
}) {
|
|
3209
|
+
return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
|
|
3210
|
+
}
|
|
3211
|
+
async persistWorkflowSnapshot({
|
|
3212
|
+
workflowName,
|
|
3213
|
+
runId,
|
|
3214
|
+
resourceId,
|
|
3215
|
+
snapshot
|
|
3216
|
+
}) {
|
|
3217
|
+
return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
|
|
3218
|
+
}
|
|
3219
|
+
async loadWorkflowSnapshot({
|
|
3220
|
+
workflowName,
|
|
3221
|
+
runId
|
|
3222
|
+
}) {
|
|
3223
|
+
return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
|
|
3224
|
+
}
|
|
3225
|
+
async listWorkflowRuns(args = {}) {
|
|
3226
|
+
return this.stores.workflows.listWorkflowRuns(args);
|
|
3227
|
+
}
|
|
3228
|
+
async getWorkflowRunById({
|
|
3229
|
+
runId,
|
|
3230
|
+
workflowName
|
|
3231
|
+
}) {
|
|
3232
|
+
return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
|
|
3233
|
+
}
|
|
3234
|
+
async close() {
|
|
3235
|
+
await this.pool.close();
|
|
3236
|
+
}
|
|
3237
|
+
/**
|
|
3238
|
+
* Index Management
|
|
3239
|
+
*/
|
|
3240
|
+
async createIndex(options) {
|
|
3241
|
+
return this.stores.operations.createIndex(options);
|
|
3242
|
+
}
|
|
3243
|
+
async listIndexes(tableName) {
|
|
3244
|
+
return this.stores.operations.listIndexes(tableName);
|
|
3245
|
+
}
|
|
3246
|
+
async describeIndex(indexName) {
|
|
3247
|
+
return this.stores.operations.describeIndex(indexName);
|
|
3248
|
+
}
|
|
3249
|
+
async dropIndex(indexName) {
|
|
3250
|
+
return this.stores.operations.dropIndex(indexName);
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Tracing / Observability
|
|
3254
|
+
*/
|
|
3255
|
+
getObservabilityStore() {
|
|
3256
|
+
if (!this.stores.observability) {
|
|
3257
|
+
throw new MastraError({
|
|
3258
|
+
id: createStorageErrorId("MSSQL", "OBSERVABILITY", "NOT_INITIALIZED"),
|
|
3259
|
+
domain: ErrorDomain.STORAGE,
|
|
3260
|
+
category: ErrorCategory.SYSTEM,
|
|
3261
|
+
text: "Observability storage is not initialized"
|
|
3262
|
+
});
|
|
3263
|
+
}
|
|
3264
|
+
return this.stores.observability;
|
|
3265
|
+
}
|
|
3266
|
+
async createSpan(span) {
|
|
3267
|
+
return this.getObservabilityStore().createSpan(span);
|
|
3268
|
+
}
|
|
3269
|
+
async updateSpan({
|
|
3270
|
+
spanId,
|
|
3271
|
+
traceId,
|
|
3272
|
+
updates
|
|
3273
|
+
}) {
|
|
3274
|
+
return this.getObservabilityStore().updateSpan({ spanId, traceId, updates });
|
|
3275
|
+
}
|
|
3276
|
+
async getTrace(traceId) {
|
|
3277
|
+
return this.getObservabilityStore().getTrace(traceId);
|
|
3278
|
+
}
|
|
3279
|
+
async getTracesPaginated(args) {
|
|
3280
|
+
return this.getObservabilityStore().getTracesPaginated(args);
|
|
3281
|
+
}
|
|
3282
|
+
async batchCreateSpans(args) {
|
|
3283
|
+
return this.getObservabilityStore().batchCreateSpans(args);
|
|
3284
|
+
}
|
|
3285
|
+
async batchUpdateSpans(args) {
|
|
3286
|
+
return this.getObservabilityStore().batchUpdateSpans(args);
|
|
3287
|
+
}
|
|
3288
|
+
async batchDeleteTraces(args) {
|
|
3289
|
+
return this.getObservabilityStore().batchDeleteTraces(args);
|
|
3290
|
+
}
|
|
3291
|
+
/**
|
|
3292
|
+
* Scorers
|
|
3293
|
+
*/
|
|
3294
|
+
async getScoreById({ id: _id }) {
|
|
3295
|
+
return this.stores.scores.getScoreById({ id: _id });
|
|
3296
|
+
}
|
|
3297
|
+
async listScoresByScorerId({
|
|
3298
|
+
scorerId: _scorerId,
|
|
1767
3299
|
pagination: _pagination,
|
|
1768
|
-
entityId,
|
|
1769
|
-
entityType
|
|
3300
|
+
entityId: _entityId,
|
|
3301
|
+
entityType: _entityType,
|
|
3302
|
+
source: _source
|
|
1770
3303
|
}) {
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
3304
|
+
return this.stores.scores.listScoresByScorerId({
|
|
3305
|
+
scorerId: _scorerId,
|
|
3306
|
+
pagination: _pagination,
|
|
3307
|
+
entityId: _entityId,
|
|
3308
|
+
entityType: _entityType,
|
|
3309
|
+
source: _source
|
|
1777
3310
|
});
|
|
1778
3311
|
}
|
|
1779
|
-
async
|
|
1780
|
-
|
|
3312
|
+
async saveScore(score) {
|
|
3313
|
+
return this.stores.scores.saveScore(score);
|
|
3314
|
+
}
|
|
3315
|
+
async listScoresByRunId({
|
|
3316
|
+
runId: _runId,
|
|
1781
3317
|
pagination: _pagination
|
|
1782
3318
|
}) {
|
|
1783
|
-
|
|
1784
|
-
id: "STORAGE_MONGODB_STORE_GET_SCORES_BY_RUN_ID_FAILED",
|
|
1785
|
-
domain: ErrorDomain.STORAGE,
|
|
1786
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
1787
|
-
details: { runId },
|
|
1788
|
-
text: "getScoresByRunId is not implemented yet in MongoDBStore"
|
|
1789
|
-
});
|
|
3319
|
+
return this.stores.scores.listScoresByRunId({ runId: _runId, pagination: _pagination });
|
|
1790
3320
|
}
|
|
1791
|
-
async
|
|
1792
|
-
entityId,
|
|
1793
|
-
entityType,
|
|
3321
|
+
async listScoresByEntityId({
|
|
3322
|
+
entityId: _entityId,
|
|
3323
|
+
entityType: _entityType,
|
|
1794
3324
|
pagination: _pagination
|
|
1795
3325
|
}) {
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
details: { entityId, entityType },
|
|
1801
|
-
text: "getScoresByEntityId is not implemented yet in MongoDBStore"
|
|
3326
|
+
return this.stores.scores.listScoresByEntityId({
|
|
3327
|
+
entityId: _entityId,
|
|
3328
|
+
entityType: _entityType,
|
|
3329
|
+
pagination: _pagination
|
|
1802
3330
|
});
|
|
1803
3331
|
}
|
|
1804
|
-
async
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
text: "dropTable is not implemented yet in MongoDBStore"
|
|
1811
|
-
});
|
|
3332
|
+
async listScoresBySpan({
|
|
3333
|
+
traceId,
|
|
3334
|
+
spanId,
|
|
3335
|
+
pagination: _pagination
|
|
3336
|
+
}) {
|
|
3337
|
+
return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination: _pagination });
|
|
1812
3338
|
}
|
|
1813
3339
|
};
|
|
1814
3340
|
|