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