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