@mastra/mssql 0.0.0-fix-zod4-schema-validation-20251212180638 → 0.0.0-fix-11329-windows-path-20251222155941
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 +144 -35
- package/dist/index.cjs +1975 -1929
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1975 -1929
- package/dist/index.js.map +1 -1
- package/dist/storage/{domains/operations → db}/index.d.ts +47 -2
- 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 +7 -9
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts +6 -7
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts +6 -7
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +7 -8
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +36 -28
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +5 -6
- package/dist/storage/domains/operations/index.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
2
|
-
import { MastraStorage, createStorageErrorId,
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
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';
|
|
3
|
+
import sql from 'mssql';
|
|
4
|
+
import { MastraBase } from '@mastra/core/base';
|
|
5
5
|
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
6
|
+
import { MessageList } from '@mastra/core/agent';
|
|
6
7
|
import { randomUUID } from 'crypto';
|
|
7
8
|
import { saveScorePayloadSchema } from '@mastra/core/evals';
|
|
8
9
|
|
|
@@ -16,2273 +17,2321 @@ function getTableName({ indexName, schemaName }) {
|
|
|
16
17
|
const quotedSchemaName = schemaName;
|
|
17
18
|
return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 };
|
|
31
36
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
43
|
-
} else if (key.endsWith("_lte")) {
|
|
44
|
-
const paramName = `p${paramIndex++}`;
|
|
45
|
-
const fieldName = key.slice(0, -4);
|
|
46
|
-
conditions.push(`[${parseSqlIdentifier(fieldName, "field name")}] <= @${paramName}`);
|
|
47
|
-
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
48
|
-
} else if (value === null) {
|
|
49
|
-
conditions.push(`[${parseSqlIdentifier(key, "field name")}] IS NULL`);
|
|
50
|
-
} else if (isInOperator(value)) {
|
|
51
|
-
const inValues = value.$in;
|
|
52
|
-
if (inValues.length === 0) {
|
|
53
|
-
conditions.push("1 = 0");
|
|
54
|
-
} else if (inValues.length === 1) {
|
|
55
|
-
const paramName = `p${paramIndex++}`;
|
|
56
|
-
conditions.push(`[${parseSqlIdentifier(key, "field name")}] = @${paramName}`);
|
|
57
|
-
params[paramName] = inValues[0] instanceof Date ? inValues[0].toISOString() : inValues[0];
|
|
58
|
-
} else {
|
|
59
|
-
const inParamNames = [];
|
|
60
|
-
for (const item of inValues) {
|
|
61
|
-
const paramName = `p${paramIndex++}`;
|
|
62
|
-
inParamNames.push(`@${paramName}`);
|
|
63
|
-
params[paramName] = item instanceof Date ? item.toISOString() : item;
|
|
64
|
-
}
|
|
65
|
-
conditions.push(`[${parseSqlIdentifier(key, "field name")}] IN (${inParamNames.join(", ")})`);
|
|
66
|
-
}
|
|
67
|
-
} else if (Array.isArray(value)) {
|
|
68
|
-
if (value.length === 0) {
|
|
69
|
-
conditions.push("1 = 0");
|
|
70
|
-
} else if (value.length === 1) {
|
|
71
|
-
const paramName = `p${paramIndex++}`;
|
|
72
|
-
conditions.push(`[${parseSqlIdentifier(key, "field name")}] = @${paramName}`);
|
|
73
|
-
params[paramName] = value[0] instanceof Date ? value[0].toISOString() : value[0];
|
|
74
|
-
} else {
|
|
75
|
-
const inParamNames = [];
|
|
76
|
-
for (const item of value) {
|
|
77
|
-
const paramName = `p${paramIndex++}`;
|
|
78
|
-
inParamNames.push(`@${paramName}`);
|
|
79
|
-
params[paramName] = item instanceof Date ? item.toISOString() : item;
|
|
37
|
+
var MssqlDB = class extends MastraBase {
|
|
38
|
+
pool;
|
|
39
|
+
schemaName;
|
|
40
|
+
setupSchemaPromise = null;
|
|
41
|
+
schemaSetupComplete = void 0;
|
|
42
|
+
getSqlType(type, isPrimaryKey = false, useLargeStorage = false) {
|
|
43
|
+
switch (type) {
|
|
44
|
+
case "text":
|
|
45
|
+
if (useLargeStorage) {
|
|
46
|
+
return "NVARCHAR(MAX)";
|
|
80
47
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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"),
|
|
66
|
+
domain: ErrorDomain.STORAGE,
|
|
67
|
+
category: ErrorCategory.THIRD_PARTY
|
|
68
|
+
});
|
|
87
69
|
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
result[key] = new Date(value);
|
|
110
|
-
} else if (columnSchema?.type === "timestamp" && value instanceof Date) {
|
|
111
|
-
result[key] = value;
|
|
112
|
-
} else if (columnSchema?.type === "boolean") {
|
|
113
|
-
result[key] = Boolean(value);
|
|
114
|
-
} else {
|
|
115
|
-
result[key] = value;
|
|
70
|
+
}
|
|
71
|
+
constructor({ pool, schemaName }) {
|
|
72
|
+
super({ component: "STORAGE", name: "MssqlDB" });
|
|
73
|
+
this.pool = pool;
|
|
74
|
+
this.schemaName = schemaName;
|
|
75
|
+
}
|
|
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;
|
|
87
|
+
}
|
|
88
|
+
async setupSchema() {
|
|
89
|
+
if (!this.schemaName || this.schemaSetupComplete) {
|
|
90
|
+
return;
|
|
116
91
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/storage/domains/memory/index.ts
|
|
122
|
-
var MemoryMSSQL = class extends MemoryStorage {
|
|
123
|
-
pool;
|
|
124
|
-
schema;
|
|
125
|
-
operations;
|
|
126
|
-
_parseAndFormatMessages(messages, format) {
|
|
127
|
-
const messagesWithParsedContent = messages.map((message) => {
|
|
128
|
-
if (typeof message.content === "string") {
|
|
92
|
+
if (!this.setupSchemaPromise) {
|
|
93
|
+
this.setupSchemaPromise = (async () => {
|
|
129
94
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
}
|
|
111
|
+
}
|
|
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;
|
|
133
120
|
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
|
|
138
|
-
const list = new MessageList().add(cleanMessages, "memory");
|
|
139
|
-
return format === "v2" ? list.get.all.db() : list.get.all.v1();
|
|
121
|
+
})();
|
|
122
|
+
}
|
|
123
|
+
await this.setupSchemaPromise;
|
|
140
124
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
125
|
+
async insert({
|
|
126
|
+
tableName,
|
|
127
|
+
record,
|
|
128
|
+
transaction
|
|
145
129
|
}) {
|
|
146
|
-
super();
|
|
147
|
-
this.pool = pool;
|
|
148
|
-
this.schema = schema;
|
|
149
|
-
this.operations = operations;
|
|
150
|
-
}
|
|
151
|
-
async getThreadById({ threadId }) {
|
|
152
130
|
try {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
[
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
...thread,
|
|
171
|
-
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
172
|
-
createdAt: thread.createdAt,
|
|
173
|
-
updatedAt: thread.updatedAt
|
|
174
|
-
};
|
|
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);
|
|
143
|
+
} else {
|
|
144
|
+
request.input(`param${i}`, preparedValue);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
await request.query(insertSql);
|
|
175
148
|
} catch (error) {
|
|
176
149
|
throw new MastraError(
|
|
177
150
|
{
|
|
178
|
-
id: createStorageErrorId("MSSQL", "
|
|
151
|
+
id: createStorageErrorId("MSSQL", "INSERT", "FAILED"),
|
|
179
152
|
domain: ErrorDomain.STORAGE,
|
|
180
153
|
category: ErrorCategory.THIRD_PARTY,
|
|
181
154
|
details: {
|
|
182
|
-
|
|
155
|
+
tableName
|
|
183
156
|
}
|
|
184
157
|
},
|
|
185
158
|
error
|
|
186
159
|
);
|
|
187
160
|
}
|
|
188
161
|
}
|
|
189
|
-
async
|
|
190
|
-
const
|
|
191
|
-
if (page < 0) {
|
|
192
|
-
throw new MastraError({
|
|
193
|
-
id: createStorageErrorId("MSSQL", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
|
|
194
|
-
domain: ErrorDomain.STORAGE,
|
|
195
|
-
category: ErrorCategory.USER,
|
|
196
|
-
text: "Page number must be non-negative",
|
|
197
|
-
details: {
|
|
198
|
-
resourceId,
|
|
199
|
-
page
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
const perPage = normalizePerPage(perPageInput, 100);
|
|
204
|
-
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
205
|
-
const { field, direction } = this.parseOrderBy(orderBy);
|
|
162
|
+
async clearTable({ tableName }) {
|
|
163
|
+
const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
|
|
206
164
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
threads: [],
|
|
216
|
-
total: 0,
|
|
217
|
-
page,
|
|
218
|
-
perPage: perPageForResponse,
|
|
219
|
-
hasMore: false
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
const orderByField = field === "createdAt" ? "[createdAt]" : "[updatedAt]";
|
|
223
|
-
const dir = (direction || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
|
|
224
|
-
const limitValue = perPageInput === false ? total : perPage;
|
|
225
|
-
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
226
|
-
const dataRequest = this.pool.request();
|
|
227
|
-
dataRequest.input("resourceId", resourceId);
|
|
228
|
-
dataRequest.input("offset", offset);
|
|
229
|
-
if (limitValue > 2147483647) {
|
|
230
|
-
dataRequest.input("perPage", sql3.BigInt, limitValue);
|
|
231
|
-
} else {
|
|
232
|
-
dataRequest.input("perPage", limitValue);
|
|
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
|
+
}
|
|
233
173
|
}
|
|
234
|
-
const rowsResult = await dataRequest.query(dataQuery);
|
|
235
|
-
const rows = rowsResult.recordset || [];
|
|
236
|
-
const threads = rows.map((thread) => ({
|
|
237
|
-
...thread,
|
|
238
|
-
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
239
|
-
createdAt: thread.createdAt,
|
|
240
|
-
updatedAt: thread.updatedAt
|
|
241
|
-
}));
|
|
242
|
-
return {
|
|
243
|
-
threads,
|
|
244
|
-
total,
|
|
245
|
-
page,
|
|
246
|
-
perPage: perPageForResponse,
|
|
247
|
-
hasMore: perPageInput === false ? false : offset + perPage < total
|
|
248
|
-
};
|
|
249
174
|
} catch (error) {
|
|
250
|
-
|
|
175
|
+
throw new MastraError(
|
|
251
176
|
{
|
|
252
|
-
id: createStorageErrorId("MSSQL", "
|
|
177
|
+
id: createStorageErrorId("MSSQL", "CLEAR_TABLE", "FAILED"),
|
|
253
178
|
domain: ErrorDomain.STORAGE,
|
|
254
179
|
category: ErrorCategory.THIRD_PARTY,
|
|
255
180
|
details: {
|
|
256
|
-
|
|
257
|
-
page
|
|
181
|
+
tableName
|
|
258
182
|
}
|
|
259
183
|
},
|
|
260
184
|
error
|
|
261
185
|
);
|
|
262
|
-
this.logger?.error?.(mastraError.toString());
|
|
263
|
-
this.logger?.trackException?.(mastraError);
|
|
264
|
-
return {
|
|
265
|
-
threads: [],
|
|
266
|
-
total: 0,
|
|
267
|
-
page,
|
|
268
|
-
perPage: perPageForResponse,
|
|
269
|
-
hasMore: false
|
|
270
|
-
};
|
|
271
186
|
}
|
|
272
187
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
metadata = @metadata,
|
|
284
|
-
[updatedAt] = @updatedAt
|
|
285
|
-
WHEN NOT MATCHED THEN
|
|
286
|
-
INSERT (id, [resourceId], title, metadata, [createdAt], [updatedAt])
|
|
287
|
-
VALUES (@id, @resourceId, @title, @metadata, @createdAt, @updatedAt);`;
|
|
288
|
-
const req = this.pool.request();
|
|
289
|
-
req.input("id", thread.id);
|
|
290
|
-
req.input("resourceId", thread.resourceId);
|
|
291
|
-
req.input("title", thread.title);
|
|
292
|
-
const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
|
|
293
|
-
if (metadata === null) {
|
|
294
|
-
req.input("metadata", sql3.NVarChar, null);
|
|
295
|
-
} else {
|
|
296
|
-
req.input("metadata", metadata);
|
|
297
|
-
}
|
|
298
|
-
req.input("createdAt", sql3.DateTime2, thread.createdAt);
|
|
299
|
-
req.input("updatedAt", sql3.DateTime2, thread.updatedAt);
|
|
300
|
-
await req.query(mergeSql);
|
|
301
|
-
return thread;
|
|
302
|
-
} catch (error) {
|
|
303
|
-
throw new MastraError(
|
|
304
|
-
{
|
|
305
|
-
id: createStorageErrorId("MSSQL", "SAVE_THREAD", "FAILED"),
|
|
306
|
-
domain: ErrorDomain.STORAGE,
|
|
307
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
308
|
-
details: {
|
|
309
|
-
threadId: thread.id
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
error
|
|
313
|
-
);
|
|
188
|
+
getDefaultValue(type) {
|
|
189
|
+
switch (type) {
|
|
190
|
+
case "timestamp":
|
|
191
|
+
return "DEFAULT SYSUTCDATETIME()";
|
|
192
|
+
case "jsonb":
|
|
193
|
+
return "DEFAULT N'{}'";
|
|
194
|
+
case "boolean":
|
|
195
|
+
return "DEFAULT 0";
|
|
196
|
+
default:
|
|
197
|
+
return getDefaultValue(type);
|
|
314
198
|
}
|
|
315
199
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
async updateThread({
|
|
320
|
-
id,
|
|
321
|
-
title,
|
|
322
|
-
metadata
|
|
200
|
+
async createTable({
|
|
201
|
+
tableName,
|
|
202
|
+
schema
|
|
323
203
|
}) {
|
|
324
|
-
const existingThread = await this.getThreadById({ threadId: id });
|
|
325
|
-
if (!existingThread) {
|
|
326
|
-
throw new MastraError({
|
|
327
|
-
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
328
|
-
domain: ErrorDomain.STORAGE,
|
|
329
|
-
category: ErrorCategory.USER,
|
|
330
|
-
text: `Thread ${id} not found`,
|
|
331
|
-
details: {
|
|
332
|
-
threadId: id,
|
|
333
|
-
title
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
const mergedMetadata = {
|
|
338
|
-
...existingThread.metadata,
|
|
339
|
-
...metadata
|
|
340
|
-
};
|
|
341
204
|
try {
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
+
];
|
|
221
|
+
const columns = Object.entries(schema).map(([name, def]) => {
|
|
222
|
+
const parsedName = parseSqlIdentifier(name, "column name");
|
|
223
|
+
const constraints = [];
|
|
224
|
+
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
225
|
+
if (!def.nullable) constraints.push("NOT NULL");
|
|
226
|
+
const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
|
|
227
|
+
const useLargeStorage = largeDataColumns.includes(name);
|
|
228
|
+
return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
|
|
229
|
+
}).join(",\n");
|
|
230
|
+
if (this.schemaName) {
|
|
231
|
+
await this.setupSchema();
|
|
359
232
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
})
|
|
233
|
+
const checkTableRequest = this.pool.request();
|
|
234
|
+
checkTableRequest.input(
|
|
235
|
+
"tableName",
|
|
236
|
+
getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
|
|
237
|
+
);
|
|
238
|
+
const checkTableSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName`;
|
|
239
|
+
checkTableRequest.input("schema", this.schemaName || "dbo");
|
|
240
|
+
const checkTableResult = await checkTableRequest.query(checkTableSql);
|
|
241
|
+
const tableExists = Array.isArray(checkTableResult.recordset) && checkTableResult.recordset.length > 0;
|
|
242
|
+
if (!tableExists) {
|
|
243
|
+
const createSql = `CREATE TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (
|
|
244
|
+
${columns}
|
|
245
|
+
)`;
|
|
246
|
+
await this.pool.request().query(createSql);
|
|
247
|
+
}
|
|
248
|
+
const columnCheckSql = `
|
|
249
|
+
SELECT 1 AS found
|
|
250
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
251
|
+
WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = 'seq_id'
|
|
252
|
+
`;
|
|
253
|
+
const checkColumnRequest = this.pool.request();
|
|
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
|
+
);
|
|
259
|
+
const columnResult = await checkColumnRequest.query(columnCheckSql);
|
|
260
|
+
const columnExists = Array.isArray(columnResult.recordset) && columnResult.recordset.length > 0;
|
|
261
|
+
if (!columnExists) {
|
|
262
|
+
const alterSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD seq_id BIGINT IDENTITY(1,1)`;
|
|
263
|
+
await this.pool.request().query(alterSql);
|
|
264
|
+
}
|
|
265
|
+
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
266
|
+
const constraintName = "mastra_workflow_snapshot_workflow_name_run_id_key";
|
|
267
|
+
const checkConstraintSql = `SELECT 1 AS found FROM sys.key_constraints WHERE name = @constraintName`;
|
|
268
|
+
const checkConstraintRequest = this.pool.request();
|
|
269
|
+
checkConstraintRequest.input("constraintName", constraintName);
|
|
270
|
+
const constraintResult = await checkConstraintRequest.query(checkConstraintSql);
|
|
271
|
+
const constraintExists = Array.isArray(constraintResult.recordset) && constraintResult.recordset.length > 0;
|
|
272
|
+
if (!constraintExists) {
|
|
273
|
+
const addConstraintSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD CONSTRAINT ${constraintName} UNIQUE ([workflow_name], [run_id])`;
|
|
274
|
+
await this.pool.request().query(addConstraintSql);
|
|
275
|
+
}
|
|
371
276
|
}
|
|
372
|
-
return {
|
|
373
|
-
...thread,
|
|
374
|
-
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
375
|
-
createdAt: thread.createdAt,
|
|
376
|
-
updatedAt: thread.updatedAt
|
|
377
|
-
};
|
|
378
277
|
} catch (error) {
|
|
379
278
|
throw new MastraError(
|
|
380
279
|
{
|
|
381
|
-
id: createStorageErrorId("MSSQL", "
|
|
280
|
+
id: createStorageErrorId("MSSQL", "CREATE_TABLE", "FAILED"),
|
|
382
281
|
domain: ErrorDomain.STORAGE,
|
|
383
282
|
category: ErrorCategory.THIRD_PARTY,
|
|
384
283
|
details: {
|
|
385
|
-
|
|
386
|
-
title
|
|
284
|
+
tableName
|
|
387
285
|
}
|
|
388
286
|
},
|
|
389
287
|
error
|
|
390
288
|
);
|
|
391
289
|
}
|
|
392
290
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
+
*/
|
|
297
|
+
async alterTable({
|
|
298
|
+
tableName,
|
|
299
|
+
schema,
|
|
300
|
+
ifNotExists
|
|
301
|
+
}) {
|
|
302
|
+
const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
|
|
399
303
|
try {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
304
|
+
for (const columnName of ifNotExists) {
|
|
305
|
+
if (schema[columnName]) {
|
|
306
|
+
const columnCheckRequest = this.pool.request();
|
|
307
|
+
columnCheckRequest.input("tableName", fullTableName.replace(/[[\]]/g, "").split(".").pop());
|
|
308
|
+
columnCheckRequest.input("columnName", columnName);
|
|
309
|
+
columnCheckRequest.input("schema", this.schemaName || "dbo");
|
|
310
|
+
const checkSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = @columnName`;
|
|
311
|
+
const checkResult = await columnCheckRequest.query(checkSql);
|
|
312
|
+
const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
|
|
313
|
+
if (!columnExists) {
|
|
314
|
+
const columnDef = schema[columnName];
|
|
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);
|
|
328
|
+
const nullable = columnDef.nullable === false ? "NOT NULL" : "";
|
|
329
|
+
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
|
|
330
|
+
const parsedColumnName = parseSqlIdentifier(columnName, "column name");
|
|
331
|
+
const alterSql = `ALTER TABLE ${fullTableName} ADD [${parsedColumnName}] ${sqlType} ${nullable} ${defaultValue}`.trim();
|
|
332
|
+
await this.pool.request().query(alterSql);
|
|
333
|
+
this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
406
337
|
} catch (error) {
|
|
407
|
-
await tx.rollback().catch(() => {
|
|
408
|
-
});
|
|
409
338
|
throw new MastraError(
|
|
410
339
|
{
|
|
411
|
-
id: createStorageErrorId("MSSQL", "
|
|
340
|
+
id: createStorageErrorId("MSSQL", "ALTER_TABLE", "FAILED"),
|
|
412
341
|
domain: ErrorDomain.STORAGE,
|
|
413
342
|
category: ErrorCategory.THIRD_PARTY,
|
|
414
343
|
details: {
|
|
415
|
-
|
|
344
|
+
tableName
|
|
416
345
|
}
|
|
417
346
|
},
|
|
418
347
|
error
|
|
419
348
|
);
|
|
420
349
|
}
|
|
421
350
|
}
|
|
422
|
-
async
|
|
423
|
-
if (!include || include.length === 0) return null;
|
|
424
|
-
const unionQueries = [];
|
|
425
|
-
const paramValues = [];
|
|
426
|
-
let paramIdx = 1;
|
|
427
|
-
const paramNames = [];
|
|
428
|
-
const tableName = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
|
|
429
|
-
for (const inc of include) {
|
|
430
|
-
const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
|
|
431
|
-
const pId = `@p${paramIdx}`;
|
|
432
|
-
const pPrev = `@p${paramIdx + 1}`;
|
|
433
|
-
const pNext = `@p${paramIdx + 2}`;
|
|
434
|
-
unionQueries.push(
|
|
435
|
-
`
|
|
436
|
-
SELECT
|
|
437
|
-
m.id,
|
|
438
|
-
m.content,
|
|
439
|
-
m.role,
|
|
440
|
-
m.type,
|
|
441
|
-
m.[createdAt],
|
|
442
|
-
m.thread_id AS threadId,
|
|
443
|
-
m.[resourceId],
|
|
444
|
-
m.seq_id
|
|
445
|
-
FROM (
|
|
446
|
-
SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
|
|
447
|
-
FROM ${tableName}
|
|
448
|
-
WHERE [thread_id] = (SELECT thread_id FROM ${tableName} WHERE id = ${pId})
|
|
449
|
-
) AS m
|
|
450
|
-
WHERE m.id = ${pId}
|
|
451
|
-
OR EXISTS (
|
|
452
|
-
SELECT 1
|
|
453
|
-
FROM (
|
|
454
|
-
SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
|
|
455
|
-
FROM ${tableName}
|
|
456
|
-
WHERE [thread_id] = (SELECT thread_id FROM ${tableName} WHERE id = ${pId})
|
|
457
|
-
) AS target
|
|
458
|
-
WHERE target.id = ${pId}
|
|
459
|
-
AND (
|
|
460
|
-
-- Get previous messages (messages that come BEFORE the target)
|
|
461
|
-
(m.row_num < target.row_num AND m.row_num >= target.row_num - ${pPrev})
|
|
462
|
-
OR
|
|
463
|
-
-- Get next messages (messages that come AFTER the target)
|
|
464
|
-
(m.row_num > target.row_num AND m.row_num <= target.row_num + ${pNext})
|
|
465
|
-
)
|
|
466
|
-
)
|
|
467
|
-
`
|
|
468
|
-
);
|
|
469
|
-
paramValues.push(id, withPreviousMessages, withNextMessages);
|
|
470
|
-
paramNames.push(`p${paramIdx}`, `p${paramIdx + 1}`, `p${paramIdx + 2}`);
|
|
471
|
-
paramIdx += 3;
|
|
472
|
-
}
|
|
473
|
-
const finalQuery = `
|
|
474
|
-
SELECT * FROM (
|
|
475
|
-
${unionQueries.join(" UNION ALL ")}
|
|
476
|
-
) AS union_result
|
|
477
|
-
ORDER BY [seq_id] ASC
|
|
478
|
-
`;
|
|
479
|
-
const req = this.pool.request();
|
|
480
|
-
for (let i = 0; i < paramValues.length; ++i) {
|
|
481
|
-
req.input(paramNames[i], paramValues[i]);
|
|
482
|
-
}
|
|
483
|
-
const result = await req.query(finalQuery);
|
|
484
|
-
const includedRows = result.recordset || [];
|
|
485
|
-
const seen = /* @__PURE__ */ new Set();
|
|
486
|
-
const dedupedRows = includedRows.filter((row) => {
|
|
487
|
-
if (seen.has(row.id)) return false;
|
|
488
|
-
seen.add(row.id);
|
|
489
|
-
return true;
|
|
490
|
-
});
|
|
491
|
-
return dedupedRows;
|
|
492
|
-
}
|
|
493
|
-
async listMessagesById({ messageIds }) {
|
|
494
|
-
if (messageIds.length === 0) return { messages: [] };
|
|
495
|
-
const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
|
|
496
|
-
const orderByStatement = `ORDER BY [seq_id] DESC`;
|
|
351
|
+
async load({ tableName, keys }) {
|
|
497
352
|
try {
|
|
498
|
-
|
|
499
|
-
|
|
353
|
+
const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
|
|
354
|
+
const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
|
|
355
|
+
const sqlQuery = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
|
|
500
356
|
const request = this.pool.request();
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const timeDiff = a.seq_id - b.seq_id;
|
|
508
|
-
return timeDiff;
|
|
509
|
-
});
|
|
510
|
-
const messagesWithParsedContent = rows.map((row) => {
|
|
511
|
-
if (typeof row.content === "string") {
|
|
512
|
-
try {
|
|
513
|
-
return { ...row, content: JSON.parse(row.content) };
|
|
514
|
-
} catch {
|
|
515
|
-
return row;
|
|
516
|
-
}
|
|
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);
|
|
517
363
|
}
|
|
518
|
-
return row;
|
|
519
364
|
});
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
|
|
365
|
+
const resultSet = await request.query(sqlQuery);
|
|
366
|
+
const result = resultSet.recordset[0] || null;
|
|
367
|
+
if (!result) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
371
|
+
const snapshot = result;
|
|
372
|
+
if (typeof snapshot.snapshot === "string") {
|
|
373
|
+
snapshot.snapshot = JSON.parse(snapshot.snapshot);
|
|
374
|
+
}
|
|
375
|
+
return snapshot;
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
523
378
|
} catch (error) {
|
|
524
|
-
|
|
379
|
+
throw new MastraError(
|
|
525
380
|
{
|
|
526
|
-
id: createStorageErrorId("MSSQL", "
|
|
381
|
+
id: createStorageErrorId("MSSQL", "LOAD", "FAILED"),
|
|
527
382
|
domain: ErrorDomain.STORAGE,
|
|
528
383
|
category: ErrorCategory.THIRD_PARTY,
|
|
529
384
|
details: {
|
|
530
|
-
|
|
385
|
+
tableName
|
|
531
386
|
}
|
|
532
387
|
},
|
|
533
388
|
error
|
|
534
389
|
);
|
|
535
|
-
this.logger?.error?.(mastraError.toString());
|
|
536
|
-
this.logger?.trackException?.(mastraError);
|
|
537
|
-
return { messages: [] };
|
|
538
390
|
}
|
|
539
391
|
}
|
|
540
|
-
async
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
392
|
+
async batchInsert({ tableName, records }) {
|
|
393
|
+
const transaction = this.pool.transaction();
|
|
394
|
+
try {
|
|
395
|
+
await transaction.begin();
|
|
396
|
+
for (const record of records) {
|
|
397
|
+
await this.insert({ tableName, record, transaction });
|
|
398
|
+
}
|
|
399
|
+
await transaction.commit();
|
|
400
|
+
} catch (error) {
|
|
401
|
+
await transaction.rollback();
|
|
544
402
|
throw new MastraError(
|
|
545
403
|
{
|
|
546
|
-
id: createStorageErrorId("MSSQL", "
|
|
404
|
+
id: createStorageErrorId("MSSQL", "BATCH_INSERT", "FAILED"),
|
|
547
405
|
domain: ErrorDomain.STORAGE,
|
|
548
406
|
category: ErrorCategory.THIRD_PARTY,
|
|
549
|
-
details: {
|
|
407
|
+
details: {
|
|
408
|
+
tableName,
|
|
409
|
+
numberOfRecords: records.length
|
|
410
|
+
}
|
|
550
411
|
},
|
|
551
|
-
|
|
412
|
+
error
|
|
552
413
|
);
|
|
553
414
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
id: createStorageErrorId("MSSQL", "LIST_MESSAGES", "INVALID_PAGE"),
|
|
557
|
-
domain: ErrorDomain.STORAGE,
|
|
558
|
-
category: ErrorCategory.USER,
|
|
559
|
-
text: "Page number must be non-negative",
|
|
560
|
-
details: {
|
|
561
|
-
threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
|
|
562
|
-
page
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
const perPage = normalizePerPage(perPageInput, 40);
|
|
567
|
-
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
415
|
+
}
|
|
416
|
+
async dropTable({ tableName }) {
|
|
568
417
|
try {
|
|
569
|
-
const {
|
|
570
|
-
|
|
571
|
-
const tableName = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
|
|
572
|
-
const baseQuery = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId FROM ${tableName}`;
|
|
573
|
-
const filters = {
|
|
574
|
-
thread_id: threadIds.length === 1 ? threadIds[0] : { $in: threadIds },
|
|
575
|
-
...resourceId ? { resourceId } : {},
|
|
576
|
-
...buildDateRangeFilter(filter?.dateRange, "createdAt")
|
|
577
|
-
};
|
|
578
|
-
const { sql: actualWhereClause = "", params: whereParams } = prepareWhereClause(
|
|
579
|
-
filters);
|
|
580
|
-
const bindWhereParams = (req) => {
|
|
581
|
-
Object.entries(whereParams).forEach(([paramName, paramValue]) => req.input(paramName, paramValue));
|
|
582
|
-
};
|
|
583
|
-
const countRequest = this.pool.request();
|
|
584
|
-
bindWhereParams(countRequest);
|
|
585
|
-
const countResult = await countRequest.query(`SELECT COUNT(*) as total FROM ${tableName}${actualWhereClause}`);
|
|
586
|
-
const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
|
|
587
|
-
const fetchBaseMessages = async () => {
|
|
588
|
-
const request = this.pool.request();
|
|
589
|
-
bindWhereParams(request);
|
|
590
|
-
if (perPageInput === false) {
|
|
591
|
-
const result2 = await request.query(`${baseQuery}${actualWhereClause} ${orderByStatement}`);
|
|
592
|
-
return result2.recordset || [];
|
|
593
|
-
}
|
|
594
|
-
request.input("offset", offset);
|
|
595
|
-
request.input("limit", perPage > 2147483647 ? sql3.BigInt : sql3.Int, perPage);
|
|
596
|
-
const result = await request.query(
|
|
597
|
-
`${baseQuery}${actualWhereClause} ${orderByStatement} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
|
|
598
|
-
);
|
|
599
|
-
return result.recordset || [];
|
|
600
|
-
};
|
|
601
|
-
const baseRows = perPage === 0 ? [] : await fetchBaseMessages();
|
|
602
|
-
const messages = [...baseRows];
|
|
603
|
-
const seqById = /* @__PURE__ */ new Map();
|
|
604
|
-
messages.forEach((msg) => {
|
|
605
|
-
if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
|
|
606
|
-
});
|
|
607
|
-
if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
|
|
608
|
-
return {
|
|
609
|
-
messages: [],
|
|
610
|
-
total: 0,
|
|
611
|
-
page,
|
|
612
|
-
perPage: perPageForResponse,
|
|
613
|
-
hasMore: false
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
if (include?.length) {
|
|
617
|
-
const messageIds = new Set(messages.map((m) => m.id));
|
|
618
|
-
const includeMessages = await this._getIncludedMessages({ include });
|
|
619
|
-
includeMessages?.forEach((msg) => {
|
|
620
|
-
if (!messageIds.has(msg.id)) {
|
|
621
|
-
messages.push(msg);
|
|
622
|
-
messageIds.add(msg.id);
|
|
623
|
-
if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
const parsed = this._parseAndFormatMessages(messages, "v2");
|
|
628
|
-
const mult = direction === "ASC" ? 1 : -1;
|
|
629
|
-
const finalMessages = parsed.sort((a, b) => {
|
|
630
|
-
const aVal = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
631
|
-
const bVal = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
632
|
-
if (aVal == null || bVal == null) {
|
|
633
|
-
return aVal == null && bVal == null ? a.id.localeCompare(b.id) : aVal == null ? 1 : -1;
|
|
634
|
-
}
|
|
635
|
-
const diff = (typeof aVal === "number" && typeof bVal === "number" ? aVal - bVal : String(aVal).localeCompare(String(bVal))) * mult;
|
|
636
|
-
if (diff !== 0) return diff;
|
|
637
|
-
const seqA = seqById.get(a.id);
|
|
638
|
-
const seqB = seqById.get(b.id);
|
|
639
|
-
return seqA != null && seqB != null ? (seqA - seqB) * mult : a.id.localeCompare(b.id);
|
|
640
|
-
});
|
|
641
|
-
const threadIdSet = new Set(threadIds);
|
|
642
|
-
const returnedThreadMessageCount = finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).length;
|
|
643
|
-
const hasMore = perPageInput !== false && returnedThreadMessageCount < total && offset + perPage < total;
|
|
644
|
-
return {
|
|
645
|
-
messages: finalMessages,
|
|
646
|
-
total,
|
|
647
|
-
page,
|
|
648
|
-
perPage: perPageForResponse,
|
|
649
|
-
hasMore
|
|
650
|
-
};
|
|
418
|
+
const tableNameWithSchema = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
|
|
419
|
+
await this.pool.request().query(`DROP TABLE IF EXISTS ${tableNameWithSchema}`);
|
|
651
420
|
} catch (error) {
|
|
652
|
-
|
|
421
|
+
throw new MastraError(
|
|
653
422
|
{
|
|
654
|
-
id: createStorageErrorId("MSSQL", "
|
|
423
|
+
id: createStorageErrorId("MSSQL", "DROP_TABLE", "FAILED"),
|
|
655
424
|
domain: ErrorDomain.STORAGE,
|
|
656
425
|
category: ErrorCategory.THIRD_PARTY,
|
|
657
426
|
details: {
|
|
658
|
-
|
|
659
|
-
resourceId: resourceId ?? ""
|
|
427
|
+
tableName
|
|
660
428
|
}
|
|
661
429
|
},
|
|
662
430
|
error
|
|
663
431
|
);
|
|
664
|
-
this.logger?.error?.(mastraError.toString());
|
|
665
|
-
this.logger?.trackException?.(mastraError);
|
|
666
|
-
return {
|
|
667
|
-
messages: [],
|
|
668
|
-
total: 0,
|
|
669
|
-
page,
|
|
670
|
-
perPage: perPageForResponse,
|
|
671
|
-
hasMore: false
|
|
672
|
-
};
|
|
673
432
|
}
|
|
674
433
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
domain: ErrorDomain.STORAGE,
|
|
682
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
683
|
-
text: `Thread ID is required`
|
|
684
|
-
});
|
|
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;
|
|
685
440
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
throw new MastraError({
|
|
689
|
-
id: createStorageErrorId("MSSQL", "SAVE_MESSAGES", "THREAD_NOT_FOUND"),
|
|
690
|
-
domain: ErrorDomain.STORAGE,
|
|
691
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
692
|
-
text: `Thread ${threadId} not found`,
|
|
693
|
-
details: { threadId }
|
|
694
|
-
});
|
|
441
|
+
if (value instanceof Date) {
|
|
442
|
+
return value;
|
|
695
443
|
}
|
|
696
|
-
const
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
`Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
if (!message.resourceId) {
|
|
709
|
-
throw new Error(
|
|
710
|
-
`Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
const request = transaction.request();
|
|
714
|
-
request.input("id", message.id);
|
|
715
|
-
request.input("thread_id", message.threadId);
|
|
716
|
-
request.input(
|
|
717
|
-
"content",
|
|
718
|
-
typeof message.content === "string" ? message.content : JSON.stringify(message.content)
|
|
719
|
-
);
|
|
720
|
-
request.input("createdAt", sql3.DateTime2, message.createdAt);
|
|
721
|
-
request.input("role", message.role);
|
|
722
|
-
request.input("type", message.type || "v2");
|
|
723
|
-
request.input("resourceId", message.resourceId);
|
|
724
|
-
const mergeSql = `MERGE INTO ${tableMessages} AS target
|
|
725
|
-
USING (SELECT @id AS id) AS src
|
|
726
|
-
ON target.id = src.id
|
|
727
|
-
WHEN MATCHED THEN UPDATE SET
|
|
728
|
-
thread_id = @thread_id,
|
|
729
|
-
content = @content,
|
|
730
|
-
[createdAt] = @createdAt,
|
|
731
|
-
role = @role,
|
|
732
|
-
type = @type,
|
|
733
|
-
resourceId = @resourceId
|
|
734
|
-
WHEN NOT MATCHED THEN INSERT (id, thread_id, content, [createdAt], role, type, resourceId)
|
|
735
|
-
VALUES (@id, @thread_id, @content, @createdAt, @role, @type, @resourceId);`;
|
|
736
|
-
await request.query(mergeSql);
|
|
737
|
-
}
|
|
738
|
-
const threadReq = transaction.request();
|
|
739
|
-
threadReq.input("updatedAt", sql3.DateTime2, /* @__PURE__ */ new Date());
|
|
740
|
-
threadReq.input("id", threadId);
|
|
741
|
-
await threadReq.query(`UPDATE ${tableThreads} SET [updatedAt] = @updatedAt WHERE id = @id`);
|
|
742
|
-
await transaction.commit();
|
|
743
|
-
} catch (error) {
|
|
744
|
-
await transaction.rollback();
|
|
745
|
-
throw error;
|
|
746
|
-
}
|
|
747
|
-
const messagesWithParsedContent = messages.map((message) => {
|
|
748
|
-
if (typeof message.content === "string") {
|
|
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) {
|
|
749
453
|
try {
|
|
750
|
-
|
|
454
|
+
JSON.parse(trimmed);
|
|
455
|
+
return trimmed;
|
|
751
456
|
} catch {
|
|
752
|
-
return message;
|
|
753
457
|
}
|
|
754
458
|
}
|
|
755
|
-
return
|
|
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);
|
|
468
|
+
}
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Maps TABLE_SCHEMAS types to mssql param types (used when value is null)
|
|
473
|
+
*/
|
|
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;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Update a single record in the database
|
|
499
|
+
*/
|
|
500
|
+
async update({
|
|
501
|
+
tableName,
|
|
502
|
+
keys,
|
|
503
|
+
data,
|
|
504
|
+
transaction
|
|
505
|
+
}) {
|
|
506
|
+
try {
|
|
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
|
+
});
|
|
514
|
+
}
|
|
515
|
+
if (!keys || Object.keys(keys).length === 0) {
|
|
516
|
+
throw new MastraError({
|
|
517
|
+
id: createStorageErrorId("MSSQL", "UPDATE", "EMPTY_KEYS"),
|
|
518
|
+
domain: ErrorDomain.STORAGE,
|
|
519
|
+
category: ErrorCategory.USER,
|
|
520
|
+
text: "Cannot update without keys to identify records"
|
|
521
|
+
});
|
|
522
|
+
}
|
|
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
|
+
}
|
|
756
536
|
});
|
|
757
|
-
const
|
|
758
|
-
|
|
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);
|
|
759
555
|
} catch (error) {
|
|
760
556
|
throw new MastraError(
|
|
761
557
|
{
|
|
762
|
-
id: createStorageErrorId("MSSQL", "
|
|
558
|
+
id: createStorageErrorId("MSSQL", "UPDATE", "FAILED"),
|
|
763
559
|
domain: ErrorDomain.STORAGE,
|
|
764
560
|
category: ErrorCategory.THIRD_PARTY,
|
|
765
|
-
details: {
|
|
561
|
+
details: {
|
|
562
|
+
tableName
|
|
563
|
+
}
|
|
766
564
|
},
|
|
767
565
|
error
|
|
768
566
|
);
|
|
769
567
|
}
|
|
770
568
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const idParams = messageIds.map((_, i) => `@id${i}`).join(", ");
|
|
779
|
-
let selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}`;
|
|
780
|
-
if (idParams.length > 0) {
|
|
781
|
-
selectQuery += ` WHERE id IN (${idParams})`;
|
|
782
|
-
} else {
|
|
783
|
-
return [];
|
|
784
|
-
}
|
|
785
|
-
const selectReq = this.pool.request();
|
|
786
|
-
messageIds.forEach((id, i) => selectReq.input(`id${i}`, id));
|
|
787
|
-
const existingMessagesDb = (await selectReq.query(selectQuery)).recordset;
|
|
788
|
-
if (!existingMessagesDb || existingMessagesDb.length === 0) {
|
|
789
|
-
return [];
|
|
790
|
-
}
|
|
791
|
-
const existingMessages = existingMessagesDb.map((msg) => {
|
|
792
|
-
if (typeof msg.content === "string") {
|
|
793
|
-
try {
|
|
794
|
-
msg.content = JSON.parse(msg.content);
|
|
795
|
-
} catch {
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
return msg;
|
|
799
|
-
});
|
|
800
|
-
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
569
|
+
/**
|
|
570
|
+
* Update multiple records in a single batch transaction
|
|
571
|
+
*/
|
|
572
|
+
async batchUpdate({
|
|
573
|
+
tableName,
|
|
574
|
+
updates
|
|
575
|
+
}) {
|
|
801
576
|
const transaction = this.pool.transaction();
|
|
802
577
|
try {
|
|
803
578
|
await transaction.begin();
|
|
804
|
-
for (const
|
|
805
|
-
|
|
806
|
-
if (!updatePayload) continue;
|
|
807
|
-
const { id, ...fieldsToUpdate } = updatePayload;
|
|
808
|
-
if (Object.keys(fieldsToUpdate).length === 0) continue;
|
|
809
|
-
threadIdsToUpdate.add(existingMessage.threadId);
|
|
810
|
-
if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
|
|
811
|
-
threadIdsToUpdate.add(updatePayload.threadId);
|
|
812
|
-
}
|
|
813
|
-
const setClauses = [];
|
|
814
|
-
const req = transaction.request();
|
|
815
|
-
req.input("id", id);
|
|
816
|
-
const columnMapping = { threadId: "thread_id" };
|
|
817
|
-
const updatableFields = { ...fieldsToUpdate };
|
|
818
|
-
if (updatableFields.content) {
|
|
819
|
-
const newContent = {
|
|
820
|
-
...existingMessage.content,
|
|
821
|
-
...updatableFields.content,
|
|
822
|
-
...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: { ...existingMessage.content.metadata, ...updatableFields.content.metadata } } : {}
|
|
823
|
-
};
|
|
824
|
-
setClauses.push(`content = @content`);
|
|
825
|
-
req.input("content", JSON.stringify(newContent));
|
|
826
|
-
delete updatableFields.content;
|
|
827
|
-
}
|
|
828
|
-
for (const key in updatableFields) {
|
|
829
|
-
if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
|
|
830
|
-
const dbColumn = columnMapping[key] || key;
|
|
831
|
-
setClauses.push(`[${dbColumn}] = @${dbColumn}`);
|
|
832
|
-
req.input(dbColumn, updatableFields[key]);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if (setClauses.length > 0) {
|
|
836
|
-
const updateSql = `UPDATE ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} SET ${setClauses.join(", ")} WHERE id = @id`;
|
|
837
|
-
await req.query(updateSql);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
if (threadIdsToUpdate.size > 0) {
|
|
841
|
-
const threadIdParams = Array.from(threadIdsToUpdate).map((_, i) => `@tid${i}`).join(", ");
|
|
842
|
-
const threadReq = transaction.request();
|
|
843
|
-
Array.from(threadIdsToUpdate).forEach((tid, i) => threadReq.input(`tid${i}`, tid));
|
|
844
|
-
threadReq.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
|
|
845
|
-
const threadSql = `UPDATE ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} SET updatedAt = @updatedAt WHERE id IN (${threadIdParams})`;
|
|
846
|
-
await threadReq.query(threadSql);
|
|
579
|
+
for (const { keys, data } of updates) {
|
|
580
|
+
await this.update({ tableName, keys, data, transaction });
|
|
847
581
|
}
|
|
848
582
|
await transaction.commit();
|
|
849
583
|
} catch (error) {
|
|
850
584
|
await transaction.rollback();
|
|
851
585
|
throw new MastraError(
|
|
852
586
|
{
|
|
853
|
-
id: createStorageErrorId("MSSQL", "
|
|
587
|
+
id: createStorageErrorId("MSSQL", "BATCH_UPDATE", "FAILED"),
|
|
854
588
|
domain: ErrorDomain.STORAGE,
|
|
855
|
-
category: ErrorCategory.THIRD_PARTY
|
|
589
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
590
|
+
details: {
|
|
591
|
+
tableName,
|
|
592
|
+
numberOfRecords: updates.length
|
|
593
|
+
}
|
|
856
594
|
},
|
|
857
595
|
error
|
|
858
596
|
);
|
|
859
597
|
}
|
|
860
|
-
const refetchReq = this.pool.request();
|
|
861
|
-
messageIds.forEach((id, i) => refetchReq.input(`id${i}`, id));
|
|
862
|
-
const updatedMessages = (await refetchReq.query(selectQuery)).recordset;
|
|
863
|
-
return (updatedMessages || []).map((message) => {
|
|
864
|
-
if (typeof message.content === "string") {
|
|
865
|
-
try {
|
|
866
|
-
message.content = JSON.parse(message.content);
|
|
867
|
-
} catch {
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
return message;
|
|
871
|
-
});
|
|
872
598
|
}
|
|
873
|
-
|
|
874
|
-
|
|
599
|
+
/**
|
|
600
|
+
* Delete multiple records by keys
|
|
601
|
+
*/
|
|
602
|
+
async batchDelete({ tableName, keys }) {
|
|
603
|
+
if (keys.length === 0) {
|
|
875
604
|
return;
|
|
876
605
|
}
|
|
606
|
+
const tableName_ = getTableName({
|
|
607
|
+
indexName: tableName,
|
|
608
|
+
schemaName: getSchemaName(this.schemaName)
|
|
609
|
+
});
|
|
610
|
+
const transaction = this.pool.transaction();
|
|
877
611
|
try {
|
|
878
|
-
const messageTableName = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
|
|
879
|
-
const threadTableName = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
|
|
880
|
-
const placeholders = messageIds.map((_, idx) => `@p${idx + 1}`).join(",");
|
|
881
|
-
const request = this.pool.request();
|
|
882
|
-
messageIds.forEach((id, idx) => {
|
|
883
|
-
request.input(`p${idx + 1}`, id);
|
|
884
|
-
});
|
|
885
|
-
const messages = await request.query(
|
|
886
|
-
`SELECT DISTINCT [thread_id] FROM ${messageTableName} WHERE [id] IN (${placeholders})`
|
|
887
|
-
);
|
|
888
|
-
const threadIds = messages.recordset?.map((msg) => msg.thread_id).filter(Boolean) || [];
|
|
889
|
-
const transaction = this.pool.transaction();
|
|
890
612
|
await transaction.begin();
|
|
891
|
-
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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);
|
|
902
626
|
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
try {
|
|
907
|
-
await transaction.rollback();
|
|
908
|
-
} catch {
|
|
909
|
-
}
|
|
910
|
-
throw error;
|
|
627
|
+
});
|
|
628
|
+
const deleteSql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
|
|
629
|
+
await request.query(deleteSql);
|
|
911
630
|
}
|
|
631
|
+
await transaction.commit();
|
|
912
632
|
} catch (error) {
|
|
633
|
+
await transaction.rollback();
|
|
913
634
|
throw new MastraError(
|
|
914
635
|
{
|
|
915
|
-
id: createStorageErrorId("MSSQL", "
|
|
636
|
+
id: createStorageErrorId("MSSQL", "BATCH_DELETE", "FAILED"),
|
|
916
637
|
domain: ErrorDomain.STORAGE,
|
|
917
638
|
category: ErrorCategory.THIRD_PARTY,
|
|
918
|
-
details: {
|
|
639
|
+
details: {
|
|
640
|
+
tableName,
|
|
641
|
+
numberOfRecords: keys.length
|
|
642
|
+
}
|
|
919
643
|
},
|
|
920
644
|
error
|
|
921
645
|
);
|
|
922
646
|
}
|
|
923
647
|
}
|
|
924
|
-
|
|
925
|
-
|
|
648
|
+
/**
|
|
649
|
+
* Create a new index on a table
|
|
650
|
+
*/
|
|
651
|
+
async createIndex(options) {
|
|
926
652
|
try {
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
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;
|
|
932
675
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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}`);
|
|
682
|
+
}
|
|
683
|
+
return `[${parseSqlIdentifier(colName, "column name")}] ${modifiers.join(" ")}`;
|
|
684
|
+
}
|
|
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);
|
|
940
690
|
} catch (error) {
|
|
941
|
-
|
|
691
|
+
throw new MastraError(
|
|
942
692
|
{
|
|
943
|
-
id: createStorageErrorId("MSSQL", "
|
|
693
|
+
id: createStorageErrorId("MSSQL", "INDEX_CREATE", "FAILED"),
|
|
944
694
|
domain: ErrorDomain.STORAGE,
|
|
945
695
|
category: ErrorCategory.THIRD_PARTY,
|
|
946
|
-
details: {
|
|
696
|
+
details: {
|
|
697
|
+
indexName: options.name,
|
|
698
|
+
tableName: options.table
|
|
699
|
+
}
|
|
947
700
|
},
|
|
948
701
|
error
|
|
949
702
|
);
|
|
950
|
-
this.logger?.error?.(mastraError.toString());
|
|
951
|
-
this.logger?.trackException?.(mastraError);
|
|
952
|
-
throw mastraError;
|
|
953
703
|
}
|
|
954
704
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
...resource,
|
|
960
|
-
metadata: resource.metadata
|
|
961
|
-
}
|
|
962
|
-
});
|
|
963
|
-
return resource;
|
|
964
|
-
}
|
|
965
|
-
async updateResource({
|
|
966
|
-
resourceId,
|
|
967
|
-
workingMemory,
|
|
968
|
-
metadata
|
|
969
|
-
}) {
|
|
705
|
+
/**
|
|
706
|
+
* Drop an existing index
|
|
707
|
+
*/
|
|
708
|
+
async dropIndex(indexName) {
|
|
970
709
|
try {
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
...existingResource.metadata,
|
|
987
|
-
...metadata
|
|
988
|
-
},
|
|
989
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
990
|
-
};
|
|
991
|
-
const tableName = getTableName({ indexName: TABLE_RESOURCES, schemaName: getSchemaName(this.schema) });
|
|
992
|
-
const updates = [];
|
|
993
|
-
const req = this.pool.request();
|
|
994
|
-
if (workingMemory !== void 0) {
|
|
995
|
-
updates.push("workingMemory = @workingMemory");
|
|
996
|
-
req.input("workingMemory", workingMemory);
|
|
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;
|
|
997
725
|
}
|
|
998
|
-
if (
|
|
999
|
-
|
|
1000
|
-
|
|
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
|
+
});
|
|
1001
734
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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);
|
|
1007
742
|
} catch (error) {
|
|
1008
|
-
|
|
743
|
+
throw new MastraError(
|
|
1009
744
|
{
|
|
1010
|
-
id: createStorageErrorId("MSSQL", "
|
|
745
|
+
id: createStorageErrorId("MSSQL", "INDEX_DROP", "FAILED"),
|
|
1011
746
|
domain: ErrorDomain.STORAGE,
|
|
1012
747
|
category: ErrorCategory.THIRD_PARTY,
|
|
1013
|
-
details: {
|
|
748
|
+
details: {
|
|
749
|
+
indexName
|
|
750
|
+
}
|
|
1014
751
|
},
|
|
1015
752
|
error
|
|
1016
753
|
);
|
|
1017
|
-
this.logger?.error?.(mastraError.toString());
|
|
1018
|
-
this.logger?.trackException?.(mastraError);
|
|
1019
|
-
throw mastraError;
|
|
1020
754
|
}
|
|
1021
755
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
schema;
|
|
1027
|
-
constructor({
|
|
1028
|
-
pool,
|
|
1029
|
-
operations,
|
|
1030
|
-
schema
|
|
1031
|
-
}) {
|
|
1032
|
-
super();
|
|
1033
|
-
this.pool = pool;
|
|
1034
|
-
this.operations = operations;
|
|
1035
|
-
this.schema = schema;
|
|
1036
|
-
}
|
|
1037
|
-
get tracingStrategy() {
|
|
1038
|
-
return {
|
|
1039
|
-
preferred: "batch-with-updates",
|
|
1040
|
-
supported: ["batch-with-updates", "insert-only"]
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
async createSpan(span) {
|
|
756
|
+
/**
|
|
757
|
+
* List indexes for a specific table or all tables
|
|
758
|
+
*/
|
|
759
|
+
async listIndexes(tableName) {
|
|
1044
760
|
try {
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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);
|
|
782
|
+
} else {
|
|
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
|
+
`;
|
|
797
|
+
}
|
|
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
|
+
});
|
|
824
|
+
}
|
|
825
|
+
return indexes;
|
|
1054
826
|
} catch (error) {
|
|
1055
827
|
throw new MastraError(
|
|
1056
828
|
{
|
|
1057
|
-
id: createStorageErrorId("MSSQL", "
|
|
829
|
+
id: createStorageErrorId("MSSQL", "INDEX_LIST", "FAILED"),
|
|
1058
830
|
domain: ErrorDomain.STORAGE,
|
|
1059
|
-
category: ErrorCategory.
|
|
1060
|
-
details: {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
spanType: span.spanType,
|
|
1064
|
-
spanName: span.name
|
|
1065
|
-
}
|
|
831
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
832
|
+
details: tableName ? {
|
|
833
|
+
tableName
|
|
834
|
+
} : {}
|
|
1066
835
|
},
|
|
1067
836
|
error
|
|
1068
837
|
);
|
|
1069
838
|
}
|
|
1070
839
|
}
|
|
1071
|
-
|
|
840
|
+
/**
|
|
841
|
+
* Get detailed statistics for a specific index
|
|
842
|
+
*/
|
|
843
|
+
async describeIndex(indexName) {
|
|
1072
844
|
try {
|
|
1073
|
-
const
|
|
1074
|
-
indexName: TABLE_SPANS,
|
|
1075
|
-
schemaName: getSchemaName(this.schema)
|
|
1076
|
-
});
|
|
845
|
+
const schemaName = this.schemaName || "dbo";
|
|
1077
846
|
const request = this.pool.request();
|
|
1078
|
-
request.input("
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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);
|
|
1088
869
|
if (!result.recordset || result.recordset.length === 0) {
|
|
1089
|
-
|
|
870
|
+
throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
|
|
1090
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
|
+
`);
|
|
1091
887
|
return {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
)
|
|
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
|
|
1099
898
|
};
|
|
1100
899
|
} catch (error) {
|
|
1101
900
|
throw new MastraError(
|
|
1102
901
|
{
|
|
1103
|
-
id: createStorageErrorId("MSSQL", "
|
|
902
|
+
id: createStorageErrorId("MSSQL", "INDEX_DESCRIBE", "FAILED"),
|
|
1104
903
|
domain: ErrorDomain.STORAGE,
|
|
1105
|
-
category: ErrorCategory.
|
|
904
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1106
905
|
details: {
|
|
1107
|
-
|
|
906
|
+
indexName
|
|
1108
907
|
}
|
|
1109
908
|
},
|
|
1110
909
|
error
|
|
1111
910
|
);
|
|
1112
911
|
}
|
|
1113
912
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
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"]
|
|
1123
963
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
964
|
+
];
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Creates automatic indexes for optimal query performance
|
|
968
|
+
* Uses getAutomaticIndexDefinitions() to determine which indexes to create
|
|
969
|
+
*/
|
|
970
|
+
async createAutomaticIndexes() {
|
|
971
|
+
try {
|
|
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);
|
|
978
|
+
}
|
|
1126
979
|
}
|
|
1127
|
-
await this.operations.update({
|
|
1128
|
-
tableName: TABLE_SPANS,
|
|
1129
|
-
keys: { spanId, traceId },
|
|
1130
|
-
data
|
|
1131
|
-
});
|
|
1132
980
|
} catch (error) {
|
|
1133
981
|
throw new MastraError(
|
|
1134
982
|
{
|
|
1135
|
-
id: createStorageErrorId("MSSQL", "
|
|
983
|
+
id: createStorageErrorId("MSSQL", "CREATE_PERFORMANCE_INDEXES", "FAILED"),
|
|
1136
984
|
domain: ErrorDomain.STORAGE,
|
|
1137
|
-
category: ErrorCategory.
|
|
1138
|
-
details: {
|
|
1139
|
-
spanId,
|
|
1140
|
-
traceId
|
|
1141
|
-
}
|
|
985
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1142
986
|
},
|
|
1143
987
|
error
|
|
1144
988
|
);
|
|
1145
989
|
}
|
|
1146
990
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
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;
|
|
1005
|
+
}
|
|
1006
|
+
if (dateRange?.end) {
|
|
1007
|
+
filters[`${fieldName}_lte`] = dateRange.end;
|
|
1008
|
+
}
|
|
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];
|
|
1170
1040
|
} else {
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
text: `Cannot filter by entity type: ${entityType}`
|
|
1179
|
-
});
|
|
1180
|
-
throw error;
|
|
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;
|
|
1046
|
+
}
|
|
1047
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] IN (${inParamNames.join(", ")})`);
|
|
1181
1048
|
}
|
|
1182
|
-
|
|
1183
|
-
if (
|
|
1184
|
-
|
|
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];
|
|
1185
1056
|
} else {
|
|
1186
|
-
|
|
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(", ")})`);
|
|
1187
1064
|
}
|
|
1188
|
-
|
|
1065
|
+
} else {
|
|
1066
|
+
const paramName = `p${paramIndex++}`;
|
|
1067
|
+
conditions.push(`[${parseSqlIdentifier(key, "field name")}] = @${paramName}`);
|
|
1068
|
+
params[paramName] = value instanceof Date ? value.toISOString() : value;
|
|
1189
1069
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
hasMore: false
|
|
1210
|
-
},
|
|
1211
|
-
spans: []
|
|
1212
|
-
};
|
|
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;
|
|
1213
1089
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
);
|
|
1223
|
-
const spans = dataResult.recordset.map(
|
|
1224
|
-
(row) => transformFromSqlRow({
|
|
1225
|
-
tableName: TABLE_SPANS,
|
|
1226
|
-
sqlRow: row
|
|
1227
|
-
})
|
|
1228
|
-
);
|
|
1229
|
-
return {
|
|
1230
|
-
pagination: {
|
|
1231
|
-
total,
|
|
1232
|
-
page,
|
|
1233
|
-
perPage,
|
|
1234
|
-
hasMore: (page + 1) * perPage < total
|
|
1235
|
-
},
|
|
1236
|
-
spans
|
|
1237
|
-
};
|
|
1238
|
-
} catch (error) {
|
|
1239
|
-
throw new MastraError(
|
|
1240
|
-
{
|
|
1241
|
-
id: createStorageErrorId("MSSQL", "GET_TRACES_PAGINATED", "FAILED"),
|
|
1242
|
-
domain: ErrorDomain.STORAGE,
|
|
1243
|
-
category: ErrorCategory.USER
|
|
1244
|
-
},
|
|
1245
|
-
error
|
|
1246
|
-
);
|
|
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;
|
|
1247
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;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
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();
|
|
1248
1123
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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;
|
|
1252
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 }) {
|
|
1253
1147
|
try {
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
})
|
|
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;
|
|
1163
|
+
}
|
|
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
|
+
};
|
|
1262
1170
|
} catch (error) {
|
|
1263
1171
|
throw new MastraError(
|
|
1264
1172
|
{
|
|
1265
|
-
id: createStorageErrorId("MSSQL", "
|
|
1173
|
+
id: createStorageErrorId("MSSQL", "GET_THREAD_BY_ID", "FAILED"),
|
|
1266
1174
|
domain: ErrorDomain.STORAGE,
|
|
1267
|
-
category: ErrorCategory.
|
|
1175
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1268
1176
|
details: {
|
|
1269
|
-
|
|
1177
|
+
threadId
|
|
1270
1178
|
}
|
|
1271
1179
|
},
|
|
1272
1180
|
error
|
|
1273
1181
|
);
|
|
1274
1182
|
}
|
|
1275
1183
|
}
|
|
1276
|
-
async
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
processedData.startedAt = processedData.startedAt.toISOString();
|
|
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
|
|
1288
1195
|
}
|
|
1289
|
-
return {
|
|
1290
|
-
keys: { spanId, traceId },
|
|
1291
|
-
data: processedData
|
|
1292
|
-
};
|
|
1293
|
-
});
|
|
1294
|
-
await this.operations.batchUpdate({
|
|
1295
|
-
tableName: TABLE_SPANS,
|
|
1296
|
-
updates
|
|
1297
1196
|
});
|
|
1298
|
-
} catch (error) {
|
|
1299
|
-
throw new MastraError(
|
|
1300
|
-
{
|
|
1301
|
-
id: createStorageErrorId("MSSQL", "BATCH_UPDATE_SPANS", "FAILED"),
|
|
1302
|
-
domain: ErrorDomain.STORAGE,
|
|
1303
|
-
category: ErrorCategory.USER,
|
|
1304
|
-
details: {
|
|
1305
|
-
count: args.records.length
|
|
1306
|
-
}
|
|
1307
|
-
},
|
|
1308
|
-
error
|
|
1309
|
-
);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
async batchDeleteTraces(args) {
|
|
1313
|
-
if (!args.traceIds || args.traceIds.length === 0) {
|
|
1314
|
-
return;
|
|
1315
1197
|
}
|
|
1198
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
1199
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1200
|
+
const { field, direction } = this.parseOrderBy(orderBy);
|
|
1316
1201
|
try {
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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
|
+
};
|
|
1216
|
+
}
|
|
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);
|
|
1228
|
+
}
|
|
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
|
+
};
|
|
1322
1244
|
} catch (error) {
|
|
1323
|
-
|
|
1245
|
+
const mastraError = new MastraError(
|
|
1324
1246
|
{
|
|
1325
|
-
id: createStorageErrorId("MSSQL", "
|
|
1247
|
+
id: createStorageErrorId("MSSQL", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
|
|
1326
1248
|
domain: ErrorDomain.STORAGE,
|
|
1327
|
-
category: ErrorCategory.
|
|
1249
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1328
1250
|
details: {
|
|
1329
|
-
|
|
1251
|
+
resourceId,
|
|
1252
|
+
page
|
|
1330
1253
|
}
|
|
1331
1254
|
},
|
|
1332
1255
|
error
|
|
1333
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
|
+
};
|
|
1334
1266
|
}
|
|
1335
1267
|
}
|
|
1336
|
-
}
|
|
1337
|
-
var StoreOperationsMSSQL = class extends StoreOperations {
|
|
1338
|
-
pool;
|
|
1339
|
-
schemaName;
|
|
1340
|
-
setupSchemaPromise = null;
|
|
1341
|
-
schemaSetupComplete = void 0;
|
|
1342
|
-
getSqlType(type, isPrimaryKey = false, useLargeStorage = false) {
|
|
1343
|
-
switch (type) {
|
|
1344
|
-
case "text":
|
|
1345
|
-
if (useLargeStorage) {
|
|
1346
|
-
return "NVARCHAR(MAX)";
|
|
1347
|
-
}
|
|
1348
|
-
return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(400)";
|
|
1349
|
-
case "timestamp":
|
|
1350
|
-
return "DATETIME2(7)";
|
|
1351
|
-
case "uuid":
|
|
1352
|
-
return "UNIQUEIDENTIFIER";
|
|
1353
|
-
case "jsonb":
|
|
1354
|
-
return "NVARCHAR(MAX)";
|
|
1355
|
-
case "integer":
|
|
1356
|
-
return "INT";
|
|
1357
|
-
case "bigint":
|
|
1358
|
-
return "BIGINT";
|
|
1359
|
-
case "float":
|
|
1360
|
-
return "FLOAT";
|
|
1361
|
-
case "boolean":
|
|
1362
|
-
return "BIT";
|
|
1363
|
-
default:
|
|
1364
|
-
throw new MastraError({
|
|
1365
|
-
id: createStorageErrorId("MSSQL", "TYPE", "NOT_SUPPORTED"),
|
|
1366
|
-
domain: ErrorDomain.STORAGE,
|
|
1367
|
-
category: ErrorCategory.THIRD_PARTY
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
constructor({ pool, schemaName }) {
|
|
1372
|
-
super();
|
|
1373
|
-
this.pool = pool;
|
|
1374
|
-
this.schemaName = schemaName;
|
|
1375
|
-
}
|
|
1376
|
-
async hasColumn(table, column) {
|
|
1377
|
-
const schema = this.schemaName || "dbo";
|
|
1378
|
-
const request = this.pool.request();
|
|
1379
|
-
request.input("schema", schema);
|
|
1380
|
-
request.input("table", table);
|
|
1381
|
-
request.input("column", column);
|
|
1382
|
-
request.input("columnLower", column.toLowerCase());
|
|
1383
|
-
const result = await request.query(
|
|
1384
|
-
`SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
|
|
1385
|
-
);
|
|
1386
|
-
return result.recordset.length > 0;
|
|
1387
|
-
}
|
|
1388
|
-
async setupSchema() {
|
|
1389
|
-
if (!this.schemaName || this.schemaSetupComplete) {
|
|
1390
|
-
return;
|
|
1391
|
-
}
|
|
1392
|
-
if (!this.setupSchemaPromise) {
|
|
1393
|
-
this.setupSchemaPromise = (async () => {
|
|
1394
|
-
try {
|
|
1395
|
-
const checkRequest = this.pool.request();
|
|
1396
|
-
checkRequest.input("schemaName", this.schemaName);
|
|
1397
|
-
const checkResult = await checkRequest.query(`
|
|
1398
|
-
SELECT 1 AS found FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = @schemaName
|
|
1399
|
-
`);
|
|
1400
|
-
const schemaExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
|
|
1401
|
-
if (!schemaExists) {
|
|
1402
|
-
try {
|
|
1403
|
-
await this.pool.request().query(`CREATE SCHEMA [${this.schemaName}]`);
|
|
1404
|
-
this.logger?.info?.(`Schema "${this.schemaName}" created successfully`);
|
|
1405
|
-
} catch (error) {
|
|
1406
|
-
this.logger?.error?.(`Failed to create schema "${this.schemaName}"`, { error });
|
|
1407
|
-
throw new Error(
|
|
1408
|
-
`Unable to create schema "${this.schemaName}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
|
|
1409
|
-
);
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
this.schemaSetupComplete = true;
|
|
1413
|
-
this.logger?.debug?.(`Schema "${this.schemaName}" is ready for use`);
|
|
1414
|
-
} catch (error) {
|
|
1415
|
-
this.schemaSetupComplete = void 0;
|
|
1416
|
-
this.setupSchemaPromise = null;
|
|
1417
|
-
throw error;
|
|
1418
|
-
} finally {
|
|
1419
|
-
this.setupSchemaPromise = null;
|
|
1420
|
-
}
|
|
1421
|
-
})();
|
|
1422
|
-
}
|
|
1423
|
-
await this.setupSchemaPromise;
|
|
1424
|
-
}
|
|
1425
|
-
async insert({
|
|
1426
|
-
tableName,
|
|
1427
|
-
record,
|
|
1428
|
-
transaction
|
|
1429
|
-
}) {
|
|
1268
|
+
async saveThread({ thread }) {
|
|
1430
1269
|
try {
|
|
1431
|
-
const
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
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);
|
|
1292
|
+
}
|
|
1293
|
+
req.input("createdAt", sql.DateTime2, thread.createdAt);
|
|
1294
|
+
req.input("updatedAt", sql.DateTime2, thread.updatedAt);
|
|
1295
|
+
await req.query(mergeSql);
|
|
1296
|
+
return thread;
|
|
1448
1297
|
} catch (error) {
|
|
1449
1298
|
throw new MastraError(
|
|
1450
1299
|
{
|
|
1451
|
-
id: createStorageErrorId("MSSQL", "
|
|
1300
|
+
id: createStorageErrorId("MSSQL", "SAVE_THREAD", "FAILED"),
|
|
1452
1301
|
domain: ErrorDomain.STORAGE,
|
|
1453
1302
|
category: ErrorCategory.THIRD_PARTY,
|
|
1454
1303
|
details: {
|
|
1455
|
-
|
|
1304
|
+
threadId: thread.id
|
|
1456
1305
|
}
|
|
1457
1306
|
},
|
|
1458
1307
|
error
|
|
1459
1308
|
);
|
|
1460
1309
|
}
|
|
1461
1310
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|
|
1318
|
+
}) {
|
|
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
|
|
1472
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
|
+
});
|
|
1473
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
|
+
};
|
|
1474
1373
|
} catch (error) {
|
|
1475
1374
|
throw new MastraError(
|
|
1476
1375
|
{
|
|
1477
|
-
id: createStorageErrorId("MSSQL", "
|
|
1376
|
+
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "FAILED"),
|
|
1478
1377
|
domain: ErrorDomain.STORAGE,
|
|
1479
1378
|
category: ErrorCategory.THIRD_PARTY,
|
|
1480
1379
|
details: {
|
|
1481
|
-
|
|
1380
|
+
threadId: id,
|
|
1381
|
+
title
|
|
1482
1382
|
}
|
|
1483
1383
|
},
|
|
1484
1384
|
error
|
|
1485
1385
|
);
|
|
1486
1386
|
}
|
|
1487
1387
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
case "boolean":
|
|
1495
|
-
return "DEFAULT 0";
|
|
1496
|
-
default:
|
|
1497
|
-
return super.getDefaultValue(type);
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
async createTable({
|
|
1501
|
-
tableName,
|
|
1502
|
-
schema
|
|
1503
|
-
}) {
|
|
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();
|
|
1504
1394
|
try {
|
|
1505
|
-
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
1525
|
-
if (!def.nullable) constraints.push("NOT NULL");
|
|
1526
|
-
const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
|
|
1527
|
-
const useLargeStorage = largeDataColumns.includes(name);
|
|
1528
|
-
return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
|
|
1529
|
-
}).join(",\n");
|
|
1530
|
-
if (this.schemaName) {
|
|
1531
|
-
await this.setupSchema();
|
|
1532
|
-
}
|
|
1533
|
-
const checkTableRequest = this.pool.request();
|
|
1534
|
-
checkTableRequest.input(
|
|
1535
|
-
"tableName",
|
|
1536
|
-
getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
|
|
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
|
|
1537
1414
|
);
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
const
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
+
`
|
|
1558
1463
|
);
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
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
|
+
}
|
|
1575
1512
|
}
|
|
1576
|
-
|
|
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() };
|
|
1577
1518
|
} catch (error) {
|
|
1578
|
-
|
|
1519
|
+
const mastraError = new MastraError(
|
|
1579
1520
|
{
|
|
1580
|
-
id: createStorageErrorId("MSSQL", "
|
|
1521
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES_BY_ID", "FAILED"),
|
|
1581
1522
|
domain: ErrorDomain.STORAGE,
|
|
1582
1523
|
category: ErrorCategory.THIRD_PARTY,
|
|
1583
1524
|
details: {
|
|
1584
|
-
|
|
1525
|
+
messageIds: JSON.stringify(messageIds)
|
|
1585
1526
|
}
|
|
1586
1527
|
},
|
|
1587
1528
|
error
|
|
1588
1529
|
);
|
|
1530
|
+
this.logger?.error?.(mastraError.toString());
|
|
1531
|
+
this.logger?.trackException?.(mastraError);
|
|
1532
|
+
return { messages: [] };
|
|
1589
1533
|
}
|
|
1590
1534
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
* @param ifNotExists Array of column names to add if they don't exist
|
|
1596
|
-
*/
|
|
1597
|
-
async alterTable({
|
|
1598
|
-
tableName,
|
|
1599
|
-
schema,
|
|
1600
|
-
ifNotExists
|
|
1601
|
-
}) {
|
|
1602
|
-
const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
|
|
1603
|
-
try {
|
|
1604
|
-
for (const columnName of ifNotExists) {
|
|
1605
|
-
if (schema[columnName]) {
|
|
1606
|
-
const columnCheckRequest = this.pool.request();
|
|
1607
|
-
columnCheckRequest.input("tableName", fullTableName.replace(/[[\]]/g, "").split(".").pop());
|
|
1608
|
-
columnCheckRequest.input("columnName", columnName);
|
|
1609
|
-
columnCheckRequest.input("schema", this.schemaName || "dbo");
|
|
1610
|
-
const checkSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = @columnName`;
|
|
1611
|
-
const checkResult = await columnCheckRequest.query(checkSql);
|
|
1612
|
-
const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
|
|
1613
|
-
if (!columnExists) {
|
|
1614
|
-
const columnDef = schema[columnName];
|
|
1615
|
-
const largeDataColumns = [
|
|
1616
|
-
"workingMemory",
|
|
1617
|
-
"snapshot",
|
|
1618
|
-
"metadata",
|
|
1619
|
-
"content",
|
|
1620
|
-
"input",
|
|
1621
|
-
"output",
|
|
1622
|
-
"instructions",
|
|
1623
|
-
"other"
|
|
1624
|
-
];
|
|
1625
|
-
const useLargeStorage = largeDataColumns.includes(columnName);
|
|
1626
|
-
const isIndexed = !!columnDef.primaryKey;
|
|
1627
|
-
const sqlType = this.getSqlType(columnDef.type, isIndexed, useLargeStorage);
|
|
1628
|
-
const nullable = columnDef.nullable === false ? "NOT NULL" : "";
|
|
1629
|
-
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
|
|
1630
|
-
const parsedColumnName = parseSqlIdentifier(columnName, "column name");
|
|
1631
|
-
const alterSql = `ALTER TABLE ${fullTableName} ADD [${parsedColumnName}] ${sqlType} ${nullable} ${defaultValue}`.trim();
|
|
1632
|
-
await this.pool.request().query(alterSql);
|
|
1633
|
-
this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
} catch (error) {
|
|
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())) {
|
|
1638
1539
|
throw new MastraError(
|
|
1639
1540
|
{
|
|
1640
|
-
id: createStorageErrorId("MSSQL", "
|
|
1541
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES", "INVALID_THREAD_ID"),
|
|
1641
1542
|
domain: ErrorDomain.STORAGE,
|
|
1642
1543
|
category: ErrorCategory.THIRD_PARTY,
|
|
1643
|
-
details: {
|
|
1644
|
-
tableName
|
|
1645
|
-
}
|
|
1544
|
+
details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
|
|
1646
1545
|
},
|
|
1647
|
-
|
|
1546
|
+
new Error("threadId must be a non-empty string or array of non-empty strings")
|
|
1648
1547
|
);
|
|
1649
1548
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
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);
|
|
1652
1563
|
try {
|
|
1653
|
-
const
|
|
1654
|
-
const
|
|
1655
|
-
const
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
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 || [];
|
|
1663
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);
|
|
1664
1601
|
});
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
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
|
+
};
|
|
1669
1610
|
}
|
|
1670
|
-
if (
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
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
|
+
});
|
|
1676
1621
|
}
|
|
1677
|
-
|
|
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
|
+
};
|
|
1678
1646
|
} catch (error) {
|
|
1679
|
-
|
|
1647
|
+
const mastraError = new MastraError(
|
|
1680
1648
|
{
|
|
1681
|
-
id: createStorageErrorId("MSSQL", "
|
|
1649
|
+
id: createStorageErrorId("MSSQL", "LIST_MESSAGES", "FAILED"),
|
|
1682
1650
|
domain: ErrorDomain.STORAGE,
|
|
1683
1651
|
category: ErrorCategory.THIRD_PARTY,
|
|
1684
1652
|
details: {
|
|
1685
|
-
|
|
1653
|
+
threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
|
|
1654
|
+
resourceId: resourceId ?? ""
|
|
1686
1655
|
}
|
|
1687
1656
|
},
|
|
1688
1657
|
error
|
|
1689
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
|
+
};
|
|
1690
1668
|
}
|
|
1691
1669
|
}
|
|
1692
|
-
async
|
|
1693
|
-
|
|
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) });
|
|
1694
1693
|
try {
|
|
1694
|
+
const transaction = this.pool.transaction();
|
|
1695
1695
|
await transaction.begin();
|
|
1696
|
-
|
|
1697
|
-
|
|
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;
|
|
1698
1741
|
}
|
|
1699
|
-
|
|
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() };
|
|
1700
1754
|
} catch (error) {
|
|
1701
|
-
await transaction.rollback();
|
|
1702
1755
|
throw new MastraError(
|
|
1703
1756
|
{
|
|
1704
|
-
id: createStorageErrorId("MSSQL", "
|
|
1757
|
+
id: createStorageErrorId("MSSQL", "SAVE_MESSAGES", "FAILED"),
|
|
1705
1758
|
domain: ErrorDomain.STORAGE,
|
|
1706
1759
|
category: ErrorCategory.THIRD_PARTY,
|
|
1707
|
-
details: {
|
|
1708
|
-
tableName,
|
|
1709
|
-
numberOfRecords: records.length
|
|
1710
|
-
}
|
|
1760
|
+
details: { threadId }
|
|
1711
1761
|
},
|
|
1712
1762
|
error
|
|
1713
1763
|
);
|
|
1714
1764
|
}
|
|
1715
1765
|
}
|
|
1716
|
-
async
|
|
1766
|
+
async updateMessages({
|
|
1767
|
+
messages
|
|
1768
|
+
}) {
|
|
1769
|
+
if (!messages || messages.length === 0) {
|
|
1770
|
+
return [];
|
|
1771
|
+
}
|
|
1772
|
+
const messageIds = messages.map((m) => m.id);
|
|
1773
|
+
const idParams = messageIds.map((_, i) => `@id${i}`).join(", ");
|
|
1774
|
+
let selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${getTableName2({ indexName: TABLE_MESSAGES, schemaName: getSchemaName2(this.schema) })}`;
|
|
1775
|
+
if (idParams.length > 0) {
|
|
1776
|
+
selectQuery += ` WHERE id IN (${idParams})`;
|
|
1777
|
+
} else {
|
|
1778
|
+
return [];
|
|
1779
|
+
}
|
|
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();
|
|
1717
1797
|
try {
|
|
1718
|
-
|
|
1719
|
-
|
|
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();
|
|
1720
1844
|
} catch (error) {
|
|
1845
|
+
await transaction.rollback();
|
|
1721
1846
|
throw new MastraError(
|
|
1722
1847
|
{
|
|
1723
|
-
id: createStorageErrorId("MSSQL", "
|
|
1848
|
+
id: createStorageErrorId("MSSQL", "UPDATE_MESSAGES", "FAILED"),
|
|
1724
1849
|
domain: ErrorDomain.STORAGE,
|
|
1725
|
-
category: ErrorCategory.THIRD_PARTY
|
|
1726
|
-
details: {
|
|
1727
|
-
tableName
|
|
1728
|
-
}
|
|
1850
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1729
1851
|
},
|
|
1730
1852
|
error
|
|
1731
1853
|
);
|
|
1732
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
|
+
});
|
|
1733
1867
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
prepareValue(value, columnName, tableName) {
|
|
1738
|
-
if (value === null || value === void 0) {
|
|
1739
|
-
return value;
|
|
1740
|
-
}
|
|
1741
|
-
if (value instanceof Date) {
|
|
1742
|
-
return value;
|
|
1743
|
-
}
|
|
1744
|
-
const schema = TABLE_SCHEMAS[tableName];
|
|
1745
|
-
const columnSchema = schema?.[columnName];
|
|
1746
|
-
if (columnSchema?.type === "boolean") {
|
|
1747
|
-
return value ? 1 : 0;
|
|
1868
|
+
async deleteMessages(messageIds) {
|
|
1869
|
+
if (!messageIds || messageIds.length === 0) {
|
|
1870
|
+
return;
|
|
1748
1871
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
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`);
|
|
1757
1897
|
}
|
|
1758
1898
|
}
|
|
1759
|
-
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1899
|
+
await transaction.commit();
|
|
1900
|
+
} catch (error) {
|
|
1901
|
+
try {
|
|
1902
|
+
await transaction.rollback();
|
|
1903
|
+
} catch {
|
|
1904
|
+
}
|
|
1905
|
+
throw error;
|
|
1763
1906
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
getMssqlType(tableName, columnName) {
|
|
1775
|
-
const col = TABLE_SCHEMAS[tableName]?.[columnName];
|
|
1776
|
-
switch (col?.type) {
|
|
1777
|
-
case "text":
|
|
1778
|
-
return sql3.NVarChar;
|
|
1779
|
-
case "timestamp":
|
|
1780
|
-
return sql3.DateTime2;
|
|
1781
|
-
case "uuid":
|
|
1782
|
-
return sql3.UniqueIdentifier;
|
|
1783
|
-
case "jsonb":
|
|
1784
|
-
return sql3.NVarChar;
|
|
1785
|
-
case "integer":
|
|
1786
|
-
return sql3.Int;
|
|
1787
|
-
case "bigint":
|
|
1788
|
-
return sql3.BigInt;
|
|
1789
|
-
case "float":
|
|
1790
|
-
return sql3.Float;
|
|
1791
|
-
case "boolean":
|
|
1792
|
-
return sql3.Bit;
|
|
1793
|
-
default:
|
|
1794
|
-
return sql3.NVarChar;
|
|
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
|
+
);
|
|
1795
1917
|
}
|
|
1796
1918
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
*/
|
|
1800
|
-
async update({
|
|
1801
|
-
tableName,
|
|
1802
|
-
keys,
|
|
1803
|
-
data,
|
|
1804
|
-
transaction
|
|
1805
|
-
}) {
|
|
1919
|
+
async getResourceById({ resourceId }) {
|
|
1920
|
+
const tableName = getTableName2({ indexName: TABLE_RESOURCES, schemaName: getSchemaName2(this.schema) });
|
|
1806
1921
|
try {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
text: "Cannot update with empty data payload"
|
|
1813
|
-
});
|
|
1814
|
-
}
|
|
1815
|
-
if (!keys || Object.keys(keys).length === 0) {
|
|
1816
|
-
throw new MastraError({
|
|
1817
|
-
id: createStorageErrorId("MSSQL", "UPDATE", "EMPTY_KEYS"),
|
|
1818
|
-
domain: ErrorDomain.STORAGE,
|
|
1819
|
-
category: ErrorCategory.USER,
|
|
1820
|
-
text: "Cannot update without keys to identify records"
|
|
1821
|
-
});
|
|
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;
|
|
1822
1927
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1831
|
-
if (preparedValue === null || preparedValue === void 0) {
|
|
1832
|
-
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1833
|
-
} else {
|
|
1834
|
-
request.input(paramName, preparedValue);
|
|
1835
|
-
}
|
|
1836
|
-
});
|
|
1837
|
-
const whereConditions = [];
|
|
1838
|
-
Object.entries(keys).forEach(([key, value]) => {
|
|
1839
|
-
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
1840
|
-
const paramName = `where${paramIndex++}`;
|
|
1841
|
-
whereConditions.push(`[${parsedKey}] = @${paramName}`);
|
|
1842
|
-
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1843
|
-
if (preparedValue === null || preparedValue === void 0) {
|
|
1844
|
-
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1845
|
-
} else {
|
|
1846
|
-
request.input(paramName, preparedValue);
|
|
1847
|
-
}
|
|
1848
|
-
});
|
|
1849
|
-
const tableName_ = getTableName({
|
|
1850
|
-
indexName: tableName,
|
|
1851
|
-
schemaName: getSchemaName(this.schemaName)
|
|
1852
|
-
});
|
|
1853
|
-
const updateSql = `UPDATE ${tableName_} SET ${setClauses.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
|
|
1854
|
-
await request.query(updateSql);
|
|
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
|
+
};
|
|
1855
1935
|
} catch (error) {
|
|
1856
|
-
|
|
1936
|
+
const mastraError = new MastraError(
|
|
1857
1937
|
{
|
|
1858
|
-
id: createStorageErrorId("MSSQL", "
|
|
1938
|
+
id: createStorageErrorId("MSSQL", "GET_RESOURCE_BY_ID", "FAILED"),
|
|
1859
1939
|
domain: ErrorDomain.STORAGE,
|
|
1860
1940
|
category: ErrorCategory.THIRD_PARTY,
|
|
1861
|
-
details: {
|
|
1862
|
-
tableName
|
|
1863
|
-
}
|
|
1941
|
+
details: { resourceId }
|
|
1864
1942
|
},
|
|
1865
1943
|
error
|
|
1866
1944
|
);
|
|
1945
|
+
this.logger?.error?.(mastraError.toString());
|
|
1946
|
+
this.logger?.trackException?.(mastraError);
|
|
1947
|
+
throw mastraError;
|
|
1867
1948
|
}
|
|
1868
1949
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
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
|
|
1875
1964
|
}) {
|
|
1876
|
-
const transaction = this.pool.transaction();
|
|
1877
1965
|
try {
|
|
1878
|
-
await
|
|
1879
|
-
|
|
1880
|
-
|
|
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 });
|
|
1881
1976
|
}
|
|
1882
|
-
|
|
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;
|
|
1883
2002
|
} catch (error) {
|
|
1884
|
-
|
|
1885
|
-
throw new MastraError(
|
|
2003
|
+
const mastraError = new MastraError(
|
|
1886
2004
|
{
|
|
1887
|
-
id: createStorageErrorId("MSSQL", "
|
|
2005
|
+
id: createStorageErrorId("MSSQL", "UPDATE_RESOURCE", "FAILED"),
|
|
1888
2006
|
domain: ErrorDomain.STORAGE,
|
|
1889
2007
|
category: ErrorCategory.THIRD_PARTY,
|
|
1890
|
-
details: {
|
|
1891
|
-
tableName,
|
|
1892
|
-
numberOfRecords: updates.length
|
|
1893
|
-
}
|
|
2008
|
+
details: { resourceId }
|
|
1894
2009
|
},
|
|
1895
2010
|
error
|
|
1896
2011
|
);
|
|
2012
|
+
this.logger?.error?.(mastraError.toString());
|
|
2013
|
+
this.logger?.trackException?.(mastraError);
|
|
2014
|
+
throw mastraError;
|
|
1897
2015
|
}
|
|
1898
2016
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
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;
|
|
1905
2035
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
});
|
|
1910
|
-
|
|
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) {
|
|
1911
2048
|
try {
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
const preparedValue = this.prepareValue(value, key, tableName);
|
|
1922
|
-
if (preparedValue === null || preparedValue === void 0) {
|
|
1923
|
-
request.input(paramName, this.getMssqlType(tableName, key), null);
|
|
1924
|
-
} else {
|
|
1925
|
-
request.input(paramName, preparedValue);
|
|
1926
|
-
}
|
|
1927
|
-
});
|
|
1928
|
-
const deleteSql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
|
|
1929
|
-
await request.query(deleteSql);
|
|
1930
|
-
}
|
|
1931
|
-
await transaction.commit();
|
|
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 });
|
|
1932
2058
|
} catch (error) {
|
|
1933
|
-
await transaction.rollback();
|
|
1934
2059
|
throw new MastraError(
|
|
1935
2060
|
{
|
|
1936
|
-
id: createStorageErrorId("MSSQL", "
|
|
2061
|
+
id: createStorageErrorId("MSSQL", "CREATE_SPAN", "FAILED"),
|
|
1937
2062
|
domain: ErrorDomain.STORAGE,
|
|
1938
|
-
category: ErrorCategory.
|
|
2063
|
+
category: ErrorCategory.USER,
|
|
1939
2064
|
details: {
|
|
1940
|
-
|
|
1941
|
-
|
|
2065
|
+
spanId: span.spanId,
|
|
2066
|
+
traceId: span.traceId,
|
|
2067
|
+
spanType: span.spanType,
|
|
2068
|
+
spanName: span.name
|
|
1942
2069
|
}
|
|
1943
2070
|
},
|
|
1944
2071
|
error
|
|
1945
2072
|
);
|
|
1946
2073
|
}
|
|
1947
2074
|
}
|
|
1948
|
-
|
|
1949
|
-
* Create a new index on a table
|
|
1950
|
-
*/
|
|
1951
|
-
async createIndex(options) {
|
|
2075
|
+
async getTrace(traceId) {
|
|
1952
2076
|
try {
|
|
1953
|
-
const
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
indexName: table,
|
|
1957
|
-
schemaName: getSchemaName(this.schemaName)
|
|
2077
|
+
const tableName = getTableName2({
|
|
2078
|
+
indexName: TABLE_SPANS,
|
|
2079
|
+
schemaName: getSchemaName2(this.schema)
|
|
1958
2080
|
});
|
|
1959
|
-
const
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
FROM
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
`);
|
|
1973
|
-
if (indexExists.recordset && indexExists.recordset.length > 0) {
|
|
1974
|
-
return;
|
|
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;
|
|
1975
2094
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
return `[${parseSqlIdentifier(col, "column name")}]`;
|
|
1986
|
-
}).join(", ");
|
|
1987
|
-
const whereStr = where ? ` WHERE ${where}` : "";
|
|
1988
|
-
const createIndexSql = `CREATE ${uniqueStr}INDEX [${indexNameSafe}] ON ${fullTableName} (${columnsStr})${whereStr}`;
|
|
1989
|
-
await this.pool.request().query(createIndexSql);
|
|
2095
|
+
return {
|
|
2096
|
+
traceId,
|
|
2097
|
+
spans: result.recordset.map(
|
|
2098
|
+
(span) => transformFromSqlRow({
|
|
2099
|
+
tableName: TABLE_SPANS,
|
|
2100
|
+
sqlRow: span
|
|
2101
|
+
})
|
|
2102
|
+
)
|
|
2103
|
+
};
|
|
1990
2104
|
} catch (error) {
|
|
1991
2105
|
throw new MastraError(
|
|
1992
2106
|
{
|
|
1993
|
-
id: createStorageErrorId("MSSQL", "
|
|
2107
|
+
id: createStorageErrorId("MSSQL", "GET_TRACE", "FAILED"),
|
|
1994
2108
|
domain: ErrorDomain.STORAGE,
|
|
1995
|
-
category: ErrorCategory.
|
|
2109
|
+
category: ErrorCategory.USER,
|
|
1996
2110
|
details: {
|
|
1997
|
-
|
|
1998
|
-
tableName: options.table
|
|
2111
|
+
traceId
|
|
1999
2112
|
}
|
|
2000
2113
|
},
|
|
2001
2114
|
error
|
|
2002
2115
|
);
|
|
2003
2116
|
}
|
|
2004
2117
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2118
|
+
async updateSpan({
|
|
2119
|
+
spanId,
|
|
2120
|
+
traceId,
|
|
2121
|
+
updates
|
|
2122
|
+
}) {
|
|
2009
2123
|
try {
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
checkRequest.input("indexName", indexNameSafe);
|
|
2014
|
-
checkRequest.input("schemaName", schemaName);
|
|
2015
|
-
const result = await checkRequest.query(`
|
|
2016
|
-
SELECT t.name as table_name
|
|
2017
|
-
FROM sys.indexes i
|
|
2018
|
-
INNER JOIN sys.tables t ON i.object_id = t.object_id
|
|
2019
|
-
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
2020
|
-
WHERE i.name = @indexName
|
|
2021
|
-
AND s.name = @schemaName
|
|
2022
|
-
`);
|
|
2023
|
-
if (!result.recordset || result.recordset.length === 0) {
|
|
2024
|
-
return;
|
|
2124
|
+
const data = { ...updates };
|
|
2125
|
+
if (data.endedAt instanceof Date) {
|
|
2126
|
+
data.endedAt = data.endedAt.toISOString();
|
|
2025
2127
|
}
|
|
2026
|
-
if (
|
|
2027
|
-
|
|
2028
|
-
throw new MastraError({
|
|
2029
|
-
id: createStorageErrorId("MSSQL", "INDEX", "AMBIGUOUS"),
|
|
2030
|
-
domain: ErrorDomain.STORAGE,
|
|
2031
|
-
category: ErrorCategory.USER,
|
|
2032
|
-
text: `Index "${indexNameSafe}" exists on multiple tables (${tables}) in schema "${schemaName}". Please drop indexes manually or ensure unique index names.`
|
|
2033
|
-
});
|
|
2128
|
+
if (data.startedAt instanceof Date) {
|
|
2129
|
+
data.startedAt = data.startedAt.toISOString();
|
|
2034
2130
|
}
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2131
|
+
await this.db.update({
|
|
2132
|
+
tableName: TABLE_SPANS,
|
|
2133
|
+
keys: { spanId, traceId },
|
|
2134
|
+
data
|
|
2039
2135
|
});
|
|
2040
|
-
const dropSql = `DROP INDEX [${indexNameSafe}] ON ${fullTableName}`;
|
|
2041
|
-
await this.pool.request().query(dropSql);
|
|
2042
2136
|
} catch (error) {
|
|
2043
2137
|
throw new MastraError(
|
|
2044
2138
|
{
|
|
2045
|
-
id: createStorageErrorId("MSSQL", "
|
|
2139
|
+
id: createStorageErrorId("MSSQL", "UPDATE_SPAN", "FAILED"),
|
|
2046
2140
|
domain: ErrorDomain.STORAGE,
|
|
2047
|
-
category: ErrorCategory.
|
|
2141
|
+
category: ErrorCategory.USER,
|
|
2048
2142
|
details: {
|
|
2049
|
-
|
|
2143
|
+
spanId,
|
|
2144
|
+
traceId
|
|
2050
2145
|
}
|
|
2051
2146
|
},
|
|
2052
2147
|
error
|
|
2053
2148
|
);
|
|
2054
2149
|
}
|
|
2055
2150
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
GROUP BY i.name, o.name, i.is_unique
|
|
2080
|
-
`;
|
|
2081
|
-
request.input("tableName", tableName);
|
|
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}'`;
|
|
2082
2174
|
} else {
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2092
|
-
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2093
|
-
WHERE sch.name = @schemaName
|
|
2094
|
-
AND i.name IS NOT NULL
|
|
2095
|
-
GROUP BY i.name, o.name, i.is_unique
|
|
2096
|
-
`;
|
|
2097
|
-
}
|
|
2098
|
-
const result = await request.query(query);
|
|
2099
|
-
const indexes = [];
|
|
2100
|
-
for (const row of result.recordset) {
|
|
2101
|
-
const colRequest = this.pool.request();
|
|
2102
|
-
colRequest.input("indexName", row.name);
|
|
2103
|
-
colRequest.input("schemaName", schemaName);
|
|
2104
|
-
const colResult = await colRequest.query(`
|
|
2105
|
-
SELECT c.name as column_name
|
|
2106
|
-
FROM sys.indexes i
|
|
2107
|
-
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
2108
|
-
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
2109
|
-
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2110
|
-
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
|
2111
|
-
WHERE i.name = @indexName
|
|
2112
|
-
AND s.name = @schemaName
|
|
2113
|
-
ORDER BY ic.key_ordinal
|
|
2114
|
-
`);
|
|
2115
|
-
indexes.push({
|
|
2116
|
-
name: row.name,
|
|
2117
|
-
table: row.table,
|
|
2118
|
-
columns: colResult.recordset.map((c) => c.column_name),
|
|
2119
|
-
unique: row.is_unique || false,
|
|
2120
|
-
size: row.size || "0 MB",
|
|
2121
|
-
definition: ""
|
|
2122
|
-
// MSSQL doesn't store definition like PG
|
|
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}`
|
|
2123
2183
|
});
|
|
2184
|
+
throw error;
|
|
2124
2185
|
}
|
|
2125
|
-
|
|
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
|
+
};
|
|
2126
2242
|
} catch (error) {
|
|
2127
2243
|
throw new MastraError(
|
|
2128
2244
|
{
|
|
2129
|
-
id: createStorageErrorId("MSSQL", "
|
|
2245
|
+
id: createStorageErrorId("MSSQL", "GET_TRACES_PAGINATED", "FAILED"),
|
|
2130
2246
|
domain: ErrorDomain.STORAGE,
|
|
2131
|
-
category: ErrorCategory.
|
|
2132
|
-
details: tableName ? {
|
|
2133
|
-
tableName
|
|
2134
|
-
} : {}
|
|
2247
|
+
category: ErrorCategory.USER
|
|
2135
2248
|
},
|
|
2136
2249
|
error
|
|
2137
2250
|
);
|
|
2138
2251
|
}
|
|
2139
2252
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2253
|
+
async batchCreateSpans(args) {
|
|
2254
|
+
if (!args.records || args.records.length === 0) {
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2144
2257
|
try {
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
i.is_unique as is_unique,
|
|
2154
|
-
CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size,
|
|
2155
|
-
i.type_desc as method,
|
|
2156
|
-
ISNULL(us.user_scans, 0) as scans,
|
|
2157
|
-
ISNULL(us.user_seeks + us.user_scans, 0) as tuples_read,
|
|
2158
|
-
ISNULL(us.user_lookups, 0) as tuples_fetched
|
|
2159
|
-
FROM sys.indexes i
|
|
2160
|
-
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2161
|
-
INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
|
|
2162
|
-
LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
|
|
2163
|
-
LEFT JOIN sys.dm_db_index_usage_stats us ON i.object_id = us.object_id AND i.index_id = us.index_id
|
|
2164
|
-
WHERE i.name = @indexName
|
|
2165
|
-
AND sch.name = @schemaName
|
|
2166
|
-
GROUP BY i.name, o.name, i.is_unique, i.type_desc, us.user_seeks, us.user_scans, us.user_lookups
|
|
2167
|
-
`;
|
|
2168
|
-
const result = await request.query(query);
|
|
2169
|
-
if (!result.recordset || result.recordset.length === 0) {
|
|
2170
|
-
throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
|
|
2171
|
-
}
|
|
2172
|
-
const row = result.recordset[0];
|
|
2173
|
-
const colRequest = this.pool.request();
|
|
2174
|
-
colRequest.input("indexName", indexName);
|
|
2175
|
-
colRequest.input("schemaName", schemaName);
|
|
2176
|
-
const colResult = await colRequest.query(`
|
|
2177
|
-
SELECT c.name as column_name
|
|
2178
|
-
FROM sys.indexes i
|
|
2179
|
-
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
2180
|
-
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
2181
|
-
INNER JOIN sys.objects o ON i.object_id = o.object_id
|
|
2182
|
-
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
|
2183
|
-
WHERE i.name = @indexName
|
|
2184
|
-
AND s.name = @schemaName
|
|
2185
|
-
ORDER BY ic.key_ordinal
|
|
2186
|
-
`);
|
|
2187
|
-
return {
|
|
2188
|
-
name: row.name,
|
|
2189
|
-
table: row.table,
|
|
2190
|
-
columns: colResult.recordset.map((c) => c.column_name),
|
|
2191
|
-
unique: row.is_unique || false,
|
|
2192
|
-
size: row.size || "0 MB",
|
|
2193
|
-
definition: "",
|
|
2194
|
-
method: row.method?.toLowerCase() || "nonclustered",
|
|
2195
|
-
scans: Number(row.scans) || 0,
|
|
2196
|
-
tuples_read: Number(row.tuples_read) || 0,
|
|
2197
|
-
tuples_fetched: Number(row.tuples_fetched) || 0
|
|
2198
|
-
};
|
|
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
|
+
});
|
|
2199
2266
|
} catch (error) {
|
|
2200
2267
|
throw new MastraError(
|
|
2201
2268
|
{
|
|
2202
|
-
id: createStorageErrorId("MSSQL", "
|
|
2269
|
+
id: createStorageErrorId("MSSQL", "BATCH_CREATE_SPANS", "FAILED"),
|
|
2203
2270
|
domain: ErrorDomain.STORAGE,
|
|
2204
|
-
category: ErrorCategory.
|
|
2271
|
+
category: ErrorCategory.USER,
|
|
2205
2272
|
details: {
|
|
2206
|
-
|
|
2273
|
+
count: args.records.length
|
|
2207
2274
|
}
|
|
2208
2275
|
},
|
|
2209
2276
|
error
|
|
2210
2277
|
);
|
|
2211
2278
|
}
|
|
2212
2279
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
*/
|
|
2218
|
-
getAutomaticIndexDefinitions() {
|
|
2219
|
-
const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
|
|
2220
|
-
return [
|
|
2221
|
-
// Composite indexes for optimal filtering + sorting performance
|
|
2222
|
-
// NVARCHAR(400) = 800 bytes, plus BIGINT (8 bytes) = 808 bytes total (under 900-byte limit)
|
|
2223
|
-
{
|
|
2224
|
-
name: `${schemaPrefix}mastra_threads_resourceid_seqid_idx`,
|
|
2225
|
-
table: TABLE_THREADS,
|
|
2226
|
-
columns: ["resourceId", "seq_id DESC"]
|
|
2227
|
-
},
|
|
2228
|
-
{
|
|
2229
|
-
name: `${schemaPrefix}mastra_messages_thread_id_seqid_idx`,
|
|
2230
|
-
table: TABLE_MESSAGES,
|
|
2231
|
-
columns: ["thread_id", "seq_id DESC"]
|
|
2232
|
-
},
|
|
2233
|
-
{
|
|
2234
|
-
name: `${schemaPrefix}mastra_traces_name_seqid_idx`,
|
|
2235
|
-
table: TABLE_TRACES,
|
|
2236
|
-
columns: ["name", "seq_id DESC"]
|
|
2237
|
-
},
|
|
2238
|
-
{
|
|
2239
|
-
name: `${schemaPrefix}mastra_scores_trace_id_span_id_seqid_idx`,
|
|
2240
|
-
table: TABLE_SCORERS,
|
|
2241
|
-
columns: ["traceId", "spanId", "seq_id DESC"]
|
|
2242
|
-
},
|
|
2243
|
-
// Spans indexes for optimal trace querying
|
|
2244
|
-
{
|
|
2245
|
-
name: `${schemaPrefix}mastra_ai_spans_traceid_startedat_idx`,
|
|
2246
|
-
table: TABLE_SPANS,
|
|
2247
|
-
columns: ["traceId", "startedAt DESC"]
|
|
2248
|
-
},
|
|
2249
|
-
{
|
|
2250
|
-
name: `${schemaPrefix}mastra_ai_spans_parentspanid_startedat_idx`,
|
|
2251
|
-
table: TABLE_SPANS,
|
|
2252
|
-
columns: ["parentSpanId", "startedAt DESC"]
|
|
2253
|
-
},
|
|
2254
|
-
{
|
|
2255
|
-
name: `${schemaPrefix}mastra_ai_spans_name_idx`,
|
|
2256
|
-
table: TABLE_SPANS,
|
|
2257
|
-
columns: ["name"]
|
|
2258
|
-
},
|
|
2259
|
-
{
|
|
2260
|
-
name: `${schemaPrefix}mastra_ai_spans_spantype_startedat_idx`,
|
|
2261
|
-
table: TABLE_SPANS,
|
|
2262
|
-
columns: ["spanType", "startedAt DESC"]
|
|
2263
|
-
}
|
|
2264
|
-
];
|
|
2265
|
-
}
|
|
2266
|
-
/**
|
|
2267
|
-
* Creates automatic indexes for optimal query performance
|
|
2268
|
-
* Uses getAutomaticIndexDefinitions() to determine which indexes to create
|
|
2269
|
-
*/
|
|
2270
|
-
async createAutomaticIndexes() {
|
|
2280
|
+
async batchUpdateSpans(args) {
|
|
2281
|
+
if (!args.records || args.records.length === 0) {
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2271
2284
|
try {
|
|
2272
|
-
const
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
} catch (error) {
|
|
2277
|
-
this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
|
|
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();
|
|
2278
2289
|
}
|
|
2279
|
-
|
|
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
|
+
});
|
|
2280
2302
|
} catch (error) {
|
|
2281
2303
|
throw new MastraError(
|
|
2282
2304
|
{
|
|
2283
|
-
id: createStorageErrorId("MSSQL", "
|
|
2305
|
+
id: createStorageErrorId("MSSQL", "BATCH_UPDATE_SPANS", "FAILED"),
|
|
2284
2306
|
domain: ErrorDomain.STORAGE,
|
|
2285
|
-
category: ErrorCategory.
|
|
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
|
+
}
|
|
2286
2335
|
},
|
|
2287
2336
|
error
|
|
2288
2337
|
);
|
|
@@ -2296,24 +2345,33 @@ function transformScoreRow(row) {
|
|
|
2296
2345
|
}
|
|
2297
2346
|
var ScoresMSSQL = class extends ScoresStorage {
|
|
2298
2347
|
pool;
|
|
2299
|
-
|
|
2348
|
+
db;
|
|
2300
2349
|
schema;
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
operations,
|
|
2304
|
-
schema
|
|
2305
|
-
}) {
|
|
2350
|
+
needsConnect;
|
|
2351
|
+
constructor(config) {
|
|
2306
2352
|
super();
|
|
2353
|
+
const { pool, db, schema, needsConnect } = resolveMssqlConfig(config);
|
|
2307
2354
|
this.pool = pool;
|
|
2308
|
-
this.
|
|
2355
|
+
this.db = db;
|
|
2309
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 });
|
|
2310
2368
|
}
|
|
2311
2369
|
async getScoreById({ id }) {
|
|
2312
2370
|
try {
|
|
2313
2371
|
const request = this.pool.request();
|
|
2314
2372
|
request.input("p1", id);
|
|
2315
2373
|
const result = await request.query(
|
|
2316
|
-
`SELECT * FROM ${
|
|
2374
|
+
`SELECT * FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE id = @p1`
|
|
2317
2375
|
);
|
|
2318
2376
|
if (result.recordset.length === 0) {
|
|
2319
2377
|
return null;
|
|
@@ -2367,7 +2425,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
2367
2425
|
entity,
|
|
2368
2426
|
...rest
|
|
2369
2427
|
} = validatedScore;
|
|
2370
|
-
await this.
|
|
2428
|
+
await this.db.insert({
|
|
2371
2429
|
tableName: TABLE_SCORERS,
|
|
2372
2430
|
record: {
|
|
2373
2431
|
id: scoreId,
|
|
@@ -2424,7 +2482,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
2424
2482
|
paramIndex++;
|
|
2425
2483
|
}
|
|
2426
2484
|
const whereClause = conditions.join(" AND ");
|
|
2427
|
-
const tableName =
|
|
2485
|
+
const tableName = getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.schema) });
|
|
2428
2486
|
const countRequest = this.pool.request();
|
|
2429
2487
|
Object.entries(params).forEach(([key, value]) => {
|
|
2430
2488
|
countRequest.input(key, value);
|
|
@@ -2484,7 +2542,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
2484
2542
|
const request = this.pool.request();
|
|
2485
2543
|
request.input("p1", runId);
|
|
2486
2544
|
const totalResult = await request.query(
|
|
2487
|
-
`SELECT COUNT(*) as count FROM ${
|
|
2545
|
+
`SELECT COUNT(*) as count FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE [runId] = @p1`
|
|
2488
2546
|
);
|
|
2489
2547
|
const total = totalResult.recordset[0]?.count || 0;
|
|
2490
2548
|
const { page, perPage: perPageInput } = pagination;
|
|
@@ -2508,7 +2566,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
2508
2566
|
dataRequest.input("p2", limitValue);
|
|
2509
2567
|
dataRequest.input("p3", start);
|
|
2510
2568
|
const result = await dataRequest.query(
|
|
2511
|
-
`SELECT * FROM ${
|
|
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`
|
|
2512
2570
|
);
|
|
2513
2571
|
return {
|
|
2514
2572
|
pagination: {
|
|
@@ -2541,7 +2599,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
2541
2599
|
request.input("p1", entityId);
|
|
2542
2600
|
request.input("p2", entityType);
|
|
2543
2601
|
const totalResult = await request.query(
|
|
2544
|
-
`SELECT COUNT(*) as count FROM ${
|
|
2602
|
+
`SELECT COUNT(*) as count FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2`
|
|
2545
2603
|
);
|
|
2546
2604
|
const total = totalResult.recordset[0]?.count || 0;
|
|
2547
2605
|
const { page, perPage: perPageInput } = pagination;
|
|
@@ -2566,7 +2624,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
2566
2624
|
dataRequest.input("p3", limitValue);
|
|
2567
2625
|
dataRequest.input("p4", start);
|
|
2568
2626
|
const result = await dataRequest.query(
|
|
2569
|
-
`SELECT * FROM ${
|
|
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`
|
|
2570
2628
|
);
|
|
2571
2629
|
return {
|
|
2572
2630
|
pagination: {
|
|
@@ -2599,7 +2657,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
2599
2657
|
request.input("p1", traceId);
|
|
2600
2658
|
request.input("p2", spanId);
|
|
2601
2659
|
const totalResult = await request.query(
|
|
2602
|
-
`SELECT COUNT(*) as count FROM ${
|
|
2660
|
+
`SELECT COUNT(*) as count FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2`
|
|
2603
2661
|
);
|
|
2604
2662
|
const total = totalResult.recordset[0]?.count || 0;
|
|
2605
2663
|
const { page, perPage: perPageInput } = pagination;
|
|
@@ -2624,7 +2682,7 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
2624
2682
|
dataRequest.input("p3", limitValue);
|
|
2625
2683
|
dataRequest.input("p4", start);
|
|
2626
2684
|
const result = await dataRequest.query(
|
|
2627
|
-
`SELECT * FROM ${
|
|
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`
|
|
2628
2686
|
);
|
|
2629
2687
|
return {
|
|
2630
2688
|
pagination: {
|
|
@@ -2650,17 +2708,26 @@ var ScoresMSSQL = class extends ScoresStorage {
|
|
|
2650
2708
|
};
|
|
2651
2709
|
var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
2652
2710
|
pool;
|
|
2653
|
-
|
|
2711
|
+
db;
|
|
2654
2712
|
schema;
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
operations,
|
|
2658
|
-
schema
|
|
2659
|
-
}) {
|
|
2713
|
+
needsConnect;
|
|
2714
|
+
constructor(config) {
|
|
2660
2715
|
super();
|
|
2716
|
+
const { pool, db, schema, needsConnect } = resolveMssqlConfig(config);
|
|
2661
2717
|
this.pool = pool;
|
|
2662
|
-
this.
|
|
2718
|
+
this.db = db;
|
|
2663
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 });
|
|
2664
2731
|
}
|
|
2665
2732
|
parseWorkflowRun(row) {
|
|
2666
2733
|
let parsedSnapshot = row.snapshot;
|
|
@@ -2687,11 +2754,11 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2687
2754
|
result,
|
|
2688
2755
|
requestContext
|
|
2689
2756
|
}) {
|
|
2690
|
-
const table =
|
|
2757
|
+
const table = getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
|
|
2691
2758
|
const transaction = this.pool.transaction();
|
|
2692
2759
|
try {
|
|
2693
2760
|
await transaction.begin();
|
|
2694
|
-
const selectRequest = new
|
|
2761
|
+
const selectRequest = new sql.Request(transaction);
|
|
2695
2762
|
selectRequest.input("workflow_name", workflowName);
|
|
2696
2763
|
selectRequest.input("run_id", runId);
|
|
2697
2764
|
const existingSnapshotResult = await selectRequest.query(
|
|
@@ -2719,12 +2786,12 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2719
2786
|
}
|
|
2720
2787
|
snapshot.context[stepId] = result;
|
|
2721
2788
|
snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
|
|
2722
|
-
const upsertReq = new
|
|
2789
|
+
const upsertReq = new sql.Request(transaction);
|
|
2723
2790
|
upsertReq.input("workflow_name", workflowName);
|
|
2724
2791
|
upsertReq.input("run_id", runId);
|
|
2725
2792
|
upsertReq.input("snapshot", JSON.stringify(snapshot));
|
|
2726
|
-
upsertReq.input("createdAt",
|
|
2727
|
-
upsertReq.input("updatedAt",
|
|
2793
|
+
upsertReq.input("createdAt", sql.DateTime2, /* @__PURE__ */ new Date());
|
|
2794
|
+
upsertReq.input("updatedAt", sql.DateTime2, /* @__PURE__ */ new Date());
|
|
2728
2795
|
await upsertReq.query(
|
|
2729
2796
|
`MERGE ${table} AS target
|
|
2730
2797
|
USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
|
|
@@ -2760,11 +2827,11 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2760
2827
|
runId,
|
|
2761
2828
|
opts
|
|
2762
2829
|
}) {
|
|
2763
|
-
const table =
|
|
2830
|
+
const table = getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
|
|
2764
2831
|
const transaction = this.pool.transaction();
|
|
2765
2832
|
try {
|
|
2766
2833
|
await transaction.begin();
|
|
2767
|
-
const selectRequest = new
|
|
2834
|
+
const selectRequest = new sql.Request(transaction);
|
|
2768
2835
|
selectRequest.input("workflow_name", workflowName);
|
|
2769
2836
|
selectRequest.input("run_id", runId);
|
|
2770
2837
|
const existingSnapshotResult = await selectRequest.query(
|
|
@@ -2792,11 +2859,11 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2792
2859
|
);
|
|
2793
2860
|
}
|
|
2794
2861
|
const updatedSnapshot = { ...snapshot, ...opts };
|
|
2795
|
-
const updateRequest = new
|
|
2862
|
+
const updateRequest = new sql.Request(transaction);
|
|
2796
2863
|
updateRequest.input("snapshot", JSON.stringify(updatedSnapshot));
|
|
2797
2864
|
updateRequest.input("workflow_name", workflowName);
|
|
2798
2865
|
updateRequest.input("run_id", runId);
|
|
2799
|
-
updateRequest.input("updatedAt",
|
|
2866
|
+
updateRequest.input("updatedAt", sql.DateTime2, /* @__PURE__ */ new Date());
|
|
2800
2867
|
await updateRequest.query(
|
|
2801
2868
|
`UPDATE ${table} SET snapshot = @snapshot, [updatedAt] = @updatedAt WHERE workflow_name = @workflow_name AND run_id = @run_id`
|
|
2802
2869
|
);
|
|
@@ -2828,7 +2895,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2828
2895
|
resourceId,
|
|
2829
2896
|
snapshot
|
|
2830
2897
|
}) {
|
|
2831
|
-
const table =
|
|
2898
|
+
const table = getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
|
|
2832
2899
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2833
2900
|
try {
|
|
2834
2901
|
const request = this.pool.request();
|
|
@@ -2836,8 +2903,8 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2836
2903
|
request.input("run_id", runId);
|
|
2837
2904
|
request.input("resourceId", resourceId);
|
|
2838
2905
|
request.input("snapshot", JSON.stringify(snapshot));
|
|
2839
|
-
request.input("createdAt",
|
|
2840
|
-
request.input("updatedAt",
|
|
2906
|
+
request.input("createdAt", sql.DateTime2, new Date(now));
|
|
2907
|
+
request.input("updatedAt", sql.DateTime2, new Date(now));
|
|
2841
2908
|
const mergeSql = `MERGE INTO ${table} AS target
|
|
2842
2909
|
USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
|
|
2843
2910
|
ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
|
|
@@ -2868,7 +2935,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2868
2935
|
runId
|
|
2869
2936
|
}) {
|
|
2870
2937
|
try {
|
|
2871
|
-
const result = await this.
|
|
2938
|
+
const result = await this.db.load({
|
|
2872
2939
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
2873
2940
|
keys: {
|
|
2874
2941
|
workflow_name: workflowName,
|
|
@@ -2910,7 +2977,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2910
2977
|
paramMap["workflowName"] = workflowName;
|
|
2911
2978
|
}
|
|
2912
2979
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2913
|
-
const tableName =
|
|
2980
|
+
const tableName = getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
|
|
2914
2981
|
const query = `SELECT * FROM ${tableName} ${whereClause}`;
|
|
2915
2982
|
const request = this.pool.request();
|
|
2916
2983
|
Object.entries(paramMap).forEach(([key, value]) => request.input(key, value));
|
|
@@ -2935,11 +3002,11 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2935
3002
|
}
|
|
2936
3003
|
}
|
|
2937
3004
|
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
2938
|
-
const table =
|
|
3005
|
+
const table = getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
|
|
2939
3006
|
const transaction = this.pool.transaction();
|
|
2940
3007
|
try {
|
|
2941
3008
|
await transaction.begin();
|
|
2942
|
-
const deleteRequest = new
|
|
3009
|
+
const deleteRequest = new sql.Request(transaction);
|
|
2943
3010
|
deleteRequest.input("workflow_name", workflowName);
|
|
2944
3011
|
deleteRequest.input("run_id", runId);
|
|
2945
3012
|
await deleteRequest.query(`DELETE FROM ${table} WHERE workflow_name = @workflow_name AND run_id = @run_id`);
|
|
@@ -2984,7 +3051,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
2984
3051
|
paramMap["status"] = status;
|
|
2985
3052
|
}
|
|
2986
3053
|
if (resourceId) {
|
|
2987
|
-
const hasResourceId = await this.
|
|
3054
|
+
const hasResourceId = await this.db.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
|
|
2988
3055
|
if (hasResourceId) {
|
|
2989
3056
|
conditions.push(`[resourceId] = @resourceId`);
|
|
2990
3057
|
paramMap["resourceId"] = resourceId;
|
|
@@ -3002,11 +3069,11 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
3002
3069
|
}
|
|
3003
3070
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3004
3071
|
let total = 0;
|
|
3005
|
-
const tableName =
|
|
3072
|
+
const tableName = getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
|
|
3006
3073
|
const request = this.pool.request();
|
|
3007
3074
|
Object.entries(paramMap).forEach(([key, value]) => {
|
|
3008
3075
|
if (value instanceof Date) {
|
|
3009
|
-
request.input(key,
|
|
3076
|
+
request.input(key, sql.DateTime, value);
|
|
3010
3077
|
} else {
|
|
3011
3078
|
request.input(key, value);
|
|
3012
3079
|
}
|
|
@@ -3045,10 +3112,14 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
|
|
|
3045
3112
|
};
|
|
3046
3113
|
|
|
3047
3114
|
// src/storage/index.ts
|
|
3115
|
+
var isPoolConfig = (config) => {
|
|
3116
|
+
return "pool" in config;
|
|
3117
|
+
};
|
|
3048
3118
|
var MSSQLStore = class extends MastraStorage {
|
|
3049
3119
|
pool;
|
|
3050
3120
|
schema;
|
|
3051
3121
|
isConnected = null;
|
|
3122
|
+
#db;
|
|
3052
3123
|
stores;
|
|
3053
3124
|
constructor(config) {
|
|
3054
3125
|
if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
|
|
@@ -3056,10 +3127,14 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
3056
3127
|
}
|
|
3057
3128
|
super({ id: config.id, name: "MSSQLStore", disableInit: config.disableInit });
|
|
3058
3129
|
try {
|
|
3059
|
-
|
|
3130
|
+
this.schema = config.schemaName || "dbo";
|
|
3131
|
+
if (isPoolConfig(config)) {
|
|
3132
|
+
this.pool = config.pool;
|
|
3133
|
+
} else if ("connectionString" in config) {
|
|
3060
3134
|
if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
|
|
3061
3135
|
throw new Error("MSSQLStore: connectionString must be provided and cannot be empty.");
|
|
3062
3136
|
}
|
|
3137
|
+
this.pool = new sql.ConnectionPool(config.connectionString);
|
|
3063
3138
|
} else {
|
|
3064
3139
|
const required = ["server", "database", "user", "password"];
|
|
3065
3140
|
for (const key of required) {
|
|
@@ -3067,23 +3142,22 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
3067
3142
|
throw new Error(`MSSQLStore: ${key} must be provided and cannot be empty.`);
|
|
3068
3143
|
}
|
|
3069
3144
|
}
|
|
3145
|
+
this.pool = new sql.ConnectionPool({
|
|
3146
|
+
server: config.server,
|
|
3147
|
+
database: config.database,
|
|
3148
|
+
user: config.user,
|
|
3149
|
+
password: config.password,
|
|
3150
|
+
port: config.port,
|
|
3151
|
+
options: config.options || { encrypt: true, trustServerCertificate: true }
|
|
3152
|
+
});
|
|
3070
3153
|
}
|
|
3071
|
-
this
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
port: config.port,
|
|
3078
|
-
options: config.options || { encrypt: true, trustServerCertificate: true }
|
|
3079
|
-
});
|
|
3080
|
-
const operations = new StoreOperationsMSSQL({ pool: this.pool, schemaName: this.schema });
|
|
3081
|
-
const scores = new ScoresMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
3082
|
-
const workflows = new WorkflowsMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
3083
|
-
const memory = new MemoryMSSQL({ pool: this.pool, schema: this.schema, operations });
|
|
3084
|
-
const observability = new ObservabilityMSSQL({ pool: this.pool, operations, schema: this.schema });
|
|
3154
|
+
this.#db = new MssqlDB({ pool: this.pool, schemaName: this.schema });
|
|
3155
|
+
const domainConfig = { pool: this.pool, db: this.#db, schema: this.schema };
|
|
3156
|
+
const scores = new ScoresMSSQL(domainConfig);
|
|
3157
|
+
const workflows = new WorkflowsMSSQL(domainConfig);
|
|
3158
|
+
const memory = new MemoryMSSQL(domainConfig);
|
|
3159
|
+
const observability = new ObservabilityMSSQL(domainConfig);
|
|
3085
3160
|
this.stores = {
|
|
3086
|
-
operations,
|
|
3087
3161
|
scores,
|
|
3088
3162
|
workflows,
|
|
3089
3163
|
memory,
|
|
@@ -3108,7 +3182,7 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
3108
3182
|
await this.isConnected;
|
|
3109
3183
|
await super.init();
|
|
3110
3184
|
try {
|
|
3111
|
-
await this.
|
|
3185
|
+
await this.#db.createAutomaticIndexes();
|
|
3112
3186
|
} catch (indexError) {
|
|
3113
3187
|
this.logger?.warn?.("Failed to create indexes:", indexError);
|
|
3114
3188
|
}
|
|
@@ -3144,34 +3218,6 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
3144
3218
|
indexManagement: true
|
|
3145
3219
|
};
|
|
3146
3220
|
}
|
|
3147
|
-
async createTable({
|
|
3148
|
-
tableName,
|
|
3149
|
-
schema
|
|
3150
|
-
}) {
|
|
3151
|
-
return this.stores.operations.createTable({ tableName, schema });
|
|
3152
|
-
}
|
|
3153
|
-
async alterTable({
|
|
3154
|
-
tableName,
|
|
3155
|
-
schema,
|
|
3156
|
-
ifNotExists
|
|
3157
|
-
}) {
|
|
3158
|
-
return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
|
|
3159
|
-
}
|
|
3160
|
-
async clearTable({ tableName }) {
|
|
3161
|
-
return this.stores.operations.clearTable({ tableName });
|
|
3162
|
-
}
|
|
3163
|
-
async dropTable({ tableName }) {
|
|
3164
|
-
return this.stores.operations.dropTable({ tableName });
|
|
3165
|
-
}
|
|
3166
|
-
async insert({ tableName, record }) {
|
|
3167
|
-
return this.stores.operations.insert({ tableName, record });
|
|
3168
|
-
}
|
|
3169
|
-
async batchInsert({ tableName, records }) {
|
|
3170
|
-
return this.stores.operations.batchInsert({ tableName, records });
|
|
3171
|
-
}
|
|
3172
|
-
async load({ tableName, keys }) {
|
|
3173
|
-
return this.stores.operations.load({ tableName, keys });
|
|
3174
|
-
}
|
|
3175
3221
|
/**
|
|
3176
3222
|
* Memory
|
|
3177
3223
|
*/
|
|
@@ -3270,16 +3316,16 @@ var MSSQLStore = class extends MastraStorage {
|
|
|
3270
3316
|
* Index Management
|
|
3271
3317
|
*/
|
|
3272
3318
|
async createIndex(options) {
|
|
3273
|
-
return this.
|
|
3319
|
+
return this.#db.createIndex(options);
|
|
3274
3320
|
}
|
|
3275
3321
|
async listIndexes(tableName) {
|
|
3276
|
-
return this.
|
|
3322
|
+
return this.#db.listIndexes(tableName);
|
|
3277
3323
|
}
|
|
3278
3324
|
async describeIndex(indexName) {
|
|
3279
|
-
return this.
|
|
3325
|
+
return this.#db.describeIndex(indexName);
|
|
3280
3326
|
}
|
|
3281
3327
|
async dropIndex(indexName) {
|
|
3282
|
-
return this.
|
|
3328
|
+
return this.#db.dropIndex(indexName);
|
|
3283
3329
|
}
|
|
3284
3330
|
/**
|
|
3285
3331
|
* Tracing / Observability
|