@mastra/pg 0.15.2 → 0.15.3-alpha.1
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 +18 -0
- package/README.md +123 -0
- package/dist/index.cjs +353 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +354 -17
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/operations/index.d.ts +23 -1
- package/dist/storage/domains/operations/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +2 -1
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +7 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/performance-indexes/performance-test.d.ts +46 -0
- package/dist/storage/performance-indexes/performance-test.d.ts.map +1 -0
- package/dist/storage/test-utils.d.ts.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @mastra/pg
|
|
2
2
|
|
|
3
|
+
## 0.15.3-alpha.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Postgresql Storage Query Index Performance: Adds index operations and automatic indexing for Postgresql ([#7757](https://github.com/mastra-ai/mastra/pull/7757))
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`60c9cec`](https://github.com/mastra-ai/mastra/commit/60c9cec7048a79a87440f7840c383875bd710d93), [`897995e`](https://github.com/mastra-ai/mastra/commit/897995e630d572fe2891e7ede817938cabb43251)]:
|
|
10
|
+
- @mastra/core@0.16.4-alpha.2
|
|
11
|
+
|
|
12
|
+
## 0.15.3-alpha.0
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Add resource id to workflow run snapshots ([#7740](https://github.com/mastra-ai/mastra/pull/7740))
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [[`547c621`](https://github.com/mastra-ai/mastra/commit/547c62104af3f7a551b3754e9cbdf0a3fbba15e4)]:
|
|
19
|
+
- @mastra/core@0.16.4-alpha.1
|
|
20
|
+
|
|
3
21
|
## 0.15.2
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -160,6 +160,129 @@ Example filter:
|
|
|
160
160
|
- `getMessages(threadId)`: Get all messages for a thread
|
|
161
161
|
- `deleteMessages(messageIds)`: Delete specific messages
|
|
162
162
|
|
|
163
|
+
## Index Management
|
|
164
|
+
|
|
165
|
+
The PostgreSQL store provides comprehensive index management capabilities to optimize query performance.
|
|
166
|
+
|
|
167
|
+
### Automatic Performance Indexes
|
|
168
|
+
|
|
169
|
+
PostgreSQL storage automatically creates composite indexes during initialization for common query patterns:
|
|
170
|
+
|
|
171
|
+
- `mastra_threads_resourceid_createdat_idx`: (resourceId, createdAt DESC)
|
|
172
|
+
- `mastra_messages_thread_id_createdat_idx`: (thread_id, createdAt DESC)
|
|
173
|
+
- `mastra_traces_name_starttime_idx`: (name, startTime DESC)
|
|
174
|
+
- `mastra_evals_agent_name_created_at_idx`: (agent_name, created_at DESC)
|
|
175
|
+
|
|
176
|
+
These indexes significantly improve performance for filtered queries with sorting.
|
|
177
|
+
|
|
178
|
+
### Creating Custom Indexes
|
|
179
|
+
|
|
180
|
+
Create additional indexes to optimize specific query patterns:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// Basic index for common queries
|
|
184
|
+
await store.createIndex({
|
|
185
|
+
name: 'idx_threads_resource',
|
|
186
|
+
table: 'mastra_threads',
|
|
187
|
+
columns: ['resourceId'],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Composite index with sort order for filtering + sorting
|
|
191
|
+
await store.createIndex({
|
|
192
|
+
name: 'idx_messages_composite',
|
|
193
|
+
table: 'mastra_messages',
|
|
194
|
+
columns: ['thread_id', 'createdAt DESC'],
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// GIN index for JSONB columns (fast JSON queries)
|
|
198
|
+
await store.createIndex({
|
|
199
|
+
name: 'idx_traces_attributes',
|
|
200
|
+
table: 'mastra_traces',
|
|
201
|
+
columns: ['attributes'],
|
|
202
|
+
method: 'gin',
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
For more advanced use cases, you can also use:
|
|
207
|
+
|
|
208
|
+
- `unique: true` for unique constraints
|
|
209
|
+
- `where: 'condition'` for partial indexes
|
|
210
|
+
- `method: 'brin'` for time-series data
|
|
211
|
+
- `storage: { fillfactor: 90 }` for update-heavy tables
|
|
212
|
+
- `concurrent: true` for non-blocking creation (default)
|
|
213
|
+
|
|
214
|
+
### Managing Indexes
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// List all indexes
|
|
218
|
+
const allIndexes = await store.listIndexes();
|
|
219
|
+
|
|
220
|
+
// List indexes for specific table
|
|
221
|
+
const threadIndexes = await store.listIndexes('mastra_threads');
|
|
222
|
+
|
|
223
|
+
// Get detailed statistics for an index
|
|
224
|
+
const stats = await store.describeIndex('idx_threads_resource');
|
|
225
|
+
console.log(stats);
|
|
226
|
+
// {
|
|
227
|
+
// name: 'idx_threads_resource',
|
|
228
|
+
// table: 'mastra_threads',
|
|
229
|
+
// columns: ['resourceId', 'createdAt'],
|
|
230
|
+
// unique: false,
|
|
231
|
+
// size: '128 KB',
|
|
232
|
+
// definition: 'CREATE INDEX idx_threads_resource...',
|
|
233
|
+
// method: 'btree',
|
|
234
|
+
// scans: 1542, // Number of index scans
|
|
235
|
+
// tuples_read: 45230, // Tuples read via index
|
|
236
|
+
// tuples_fetched: 12050 // Tuples fetched via index
|
|
237
|
+
// }
|
|
238
|
+
|
|
239
|
+
// Drop an index
|
|
240
|
+
await store.dropIndex('idx_threads_status');
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Index Types and Use Cases
|
|
244
|
+
|
|
245
|
+
| Index Type | Best For | Storage | Speed |
|
|
246
|
+
| ------------------- | --------------------------------------- | ---------- | -------------------------- |
|
|
247
|
+
| **btree** (default) | Range queries, sorting, general purpose | Moderate | Fast |
|
|
248
|
+
| **hash** | Equality comparisons only | Small | Very fast for `=` |
|
|
249
|
+
| **gin** | JSONB, arrays, full-text search | Large | Fast for contains |
|
|
250
|
+
| **gist** | Geometric data, full-text search | Moderate | Fast for nearest-neighbor |
|
|
251
|
+
| **spgist** | Non-balanced data, text patterns | Small | Fast for specific patterns |
|
|
252
|
+
| **brin** | Large tables with natural ordering | Very small | Fast for ranges |
|
|
253
|
+
|
|
254
|
+
### Index Options
|
|
255
|
+
|
|
256
|
+
- `name` (required): Index name
|
|
257
|
+
- `table` (required): Table name
|
|
258
|
+
- `columns` (required): Array of column names (can include DESC/ASC)
|
|
259
|
+
- `unique`: Create unique index (default: false)
|
|
260
|
+
- `concurrent`: Non-blocking index creation (default: true)
|
|
261
|
+
- `where`: Partial index condition
|
|
262
|
+
- `method`: Index type ('btree' | 'hash' | 'gin' | 'gist' | 'spgist' | 'brin')
|
|
263
|
+
- `opclass`: Operator class for GIN/GIST indexes
|
|
264
|
+
- `storage`: Storage parameters (e.g., { fillfactor: 90 })
|
|
265
|
+
- `tablespace`: Tablespace name for index placement
|
|
266
|
+
|
|
267
|
+
### Monitoring Index Performance
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// Check index usage statistics
|
|
271
|
+
const stats = await store.describeIndex('idx_threads_resource');
|
|
272
|
+
|
|
273
|
+
// Identify unused indexes
|
|
274
|
+
if (stats.scans === 0) {
|
|
275
|
+
console.log(`Index ${stats.name} is unused - consider removing`);
|
|
276
|
+
await store.dropIndex(stats.name);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Monitor index efficiency
|
|
280
|
+
const efficiency = stats.tuples_fetched / stats.tuples_read;
|
|
281
|
+
if (efficiency < 0.5) {
|
|
282
|
+
console.log(`Index ${stats.name} has low efficiency: ${efficiency}`);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
163
286
|
## Related Links
|
|
164
287
|
|
|
165
288
|
- [pgvector Documentation](https://github.com/pgvector/pgvector)
|
package/dist/index.cjs
CHANGED
|
@@ -2374,6 +2374,303 @@ var StoreOperationsPG = class extends storage.StoreOperations {
|
|
|
2374
2374
|
);
|
|
2375
2375
|
}
|
|
2376
2376
|
}
|
|
2377
|
+
/**
|
|
2378
|
+
* Create a new index on a table
|
|
2379
|
+
*/
|
|
2380
|
+
async createIndex(options) {
|
|
2381
|
+
try {
|
|
2382
|
+
const {
|
|
2383
|
+
name,
|
|
2384
|
+
table,
|
|
2385
|
+
columns,
|
|
2386
|
+
unique = false,
|
|
2387
|
+
concurrent = true,
|
|
2388
|
+
where,
|
|
2389
|
+
method = "btree",
|
|
2390
|
+
opclass,
|
|
2391
|
+
storage,
|
|
2392
|
+
tablespace
|
|
2393
|
+
} = options;
|
|
2394
|
+
const schemaName = this.schemaName || "public";
|
|
2395
|
+
const fullTableName = getTableName({
|
|
2396
|
+
indexName: table,
|
|
2397
|
+
schemaName: getSchemaName(this.schemaName)
|
|
2398
|
+
});
|
|
2399
|
+
const indexExists = await this.client.oneOrNone(
|
|
2400
|
+
`SELECT 1 FROM pg_indexes
|
|
2401
|
+
WHERE indexname = $1
|
|
2402
|
+
AND schemaname = $2`,
|
|
2403
|
+
[name, schemaName]
|
|
2404
|
+
);
|
|
2405
|
+
if (indexExists) {
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
const uniqueStr = unique ? "UNIQUE " : "";
|
|
2409
|
+
const concurrentStr = concurrent ? "CONCURRENTLY " : "";
|
|
2410
|
+
const methodStr = method !== "btree" ? `USING ${method} ` : "";
|
|
2411
|
+
const columnsStr = columns.map((col) => {
|
|
2412
|
+
if (col.includes(" DESC") || col.includes(" ASC")) {
|
|
2413
|
+
const [colName, ...modifiers] = col.split(" ");
|
|
2414
|
+
if (!colName) {
|
|
2415
|
+
throw new Error(`Invalid column specification: ${col}`);
|
|
2416
|
+
}
|
|
2417
|
+
const quotedCol2 = `"${utils.parseSqlIdentifier(colName, "column name")}" ${modifiers.join(" ")}`;
|
|
2418
|
+
return opclass ? `${quotedCol2} ${opclass}` : quotedCol2;
|
|
2419
|
+
}
|
|
2420
|
+
const quotedCol = `"${utils.parseSqlIdentifier(col, "column name")}"`;
|
|
2421
|
+
return opclass ? `${quotedCol} ${opclass}` : quotedCol;
|
|
2422
|
+
}).join(", ");
|
|
2423
|
+
const whereStr = where ? ` WHERE ${where}` : "";
|
|
2424
|
+
const tablespaceStr = tablespace ? ` TABLESPACE ${tablespace}` : "";
|
|
2425
|
+
let withStr = "";
|
|
2426
|
+
if (storage && Object.keys(storage).length > 0) {
|
|
2427
|
+
const storageParams = Object.entries(storage).map(([key, value]) => `${key} = ${value}`).join(", ");
|
|
2428
|
+
withStr = ` WITH (${storageParams})`;
|
|
2429
|
+
}
|
|
2430
|
+
const sql = `CREATE ${uniqueStr}INDEX ${concurrentStr}${name} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
|
|
2431
|
+
await this.client.none(sql);
|
|
2432
|
+
} catch (error$1) {
|
|
2433
|
+
if (error$1 instanceof Error && error$1.message.includes("CONCURRENTLY")) {
|
|
2434
|
+
const retryOptions = { ...options, concurrent: false };
|
|
2435
|
+
return this.createIndex(retryOptions);
|
|
2436
|
+
}
|
|
2437
|
+
throw new error.MastraError(
|
|
2438
|
+
{
|
|
2439
|
+
id: "MASTRA_STORAGE_PG_INDEX_CREATE_FAILED",
|
|
2440
|
+
domain: error.ErrorDomain.STORAGE,
|
|
2441
|
+
category: error.ErrorCategory.THIRD_PARTY,
|
|
2442
|
+
details: {
|
|
2443
|
+
indexName: options.name,
|
|
2444
|
+
tableName: options.table
|
|
2445
|
+
}
|
|
2446
|
+
},
|
|
2447
|
+
error$1
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Drop an existing index
|
|
2453
|
+
*/
|
|
2454
|
+
async dropIndex(indexName) {
|
|
2455
|
+
try {
|
|
2456
|
+
const schemaName = this.schemaName || "public";
|
|
2457
|
+
const indexExists = await this.client.oneOrNone(
|
|
2458
|
+
`SELECT 1 FROM pg_indexes
|
|
2459
|
+
WHERE indexname = $1
|
|
2460
|
+
AND schemaname = $2`,
|
|
2461
|
+
[indexName, schemaName]
|
|
2462
|
+
);
|
|
2463
|
+
if (!indexExists) {
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
const sql = `DROP INDEX IF EXISTS ${getSchemaName(this.schemaName)}.${indexName}`;
|
|
2467
|
+
await this.client.none(sql);
|
|
2468
|
+
} catch (error$1) {
|
|
2469
|
+
throw new error.MastraError(
|
|
2470
|
+
{
|
|
2471
|
+
id: "MASTRA_STORAGE_PG_INDEX_DROP_FAILED",
|
|
2472
|
+
domain: error.ErrorDomain.STORAGE,
|
|
2473
|
+
category: error.ErrorCategory.THIRD_PARTY,
|
|
2474
|
+
details: {
|
|
2475
|
+
indexName
|
|
2476
|
+
}
|
|
2477
|
+
},
|
|
2478
|
+
error$1
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
/**
|
|
2483
|
+
* List indexes for a specific table or all tables
|
|
2484
|
+
*/
|
|
2485
|
+
async listIndexes(tableName) {
|
|
2486
|
+
try {
|
|
2487
|
+
const schemaName = this.schemaName || "public";
|
|
2488
|
+
let query;
|
|
2489
|
+
let params;
|
|
2490
|
+
if (tableName) {
|
|
2491
|
+
query = `
|
|
2492
|
+
SELECT
|
|
2493
|
+
i.indexname as name,
|
|
2494
|
+
i.tablename as table,
|
|
2495
|
+
i.indexdef as definition,
|
|
2496
|
+
ix.indisunique as is_unique,
|
|
2497
|
+
pg_size_pretty(pg_relation_size(c.oid)) as size,
|
|
2498
|
+
array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
|
|
2499
|
+
FROM pg_indexes i
|
|
2500
|
+
JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
|
|
2501
|
+
JOIN pg_index ix ON ix.indexrelid = c.oid
|
|
2502
|
+
JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
|
|
2503
|
+
WHERE i.schemaname = $1
|
|
2504
|
+
AND i.tablename = $2
|
|
2505
|
+
GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
|
|
2506
|
+
`;
|
|
2507
|
+
params = [schemaName, tableName];
|
|
2508
|
+
} else {
|
|
2509
|
+
query = `
|
|
2510
|
+
SELECT
|
|
2511
|
+
i.indexname as name,
|
|
2512
|
+
i.tablename as table,
|
|
2513
|
+
i.indexdef as definition,
|
|
2514
|
+
ix.indisunique as is_unique,
|
|
2515
|
+
pg_size_pretty(pg_relation_size(c.oid)) as size,
|
|
2516
|
+
array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
|
|
2517
|
+
FROM pg_indexes i
|
|
2518
|
+
JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
|
|
2519
|
+
JOIN pg_index ix ON ix.indexrelid = c.oid
|
|
2520
|
+
JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
|
|
2521
|
+
WHERE i.schemaname = $1
|
|
2522
|
+
GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
|
|
2523
|
+
`;
|
|
2524
|
+
params = [schemaName];
|
|
2525
|
+
}
|
|
2526
|
+
const results = await this.client.manyOrNone(query, params);
|
|
2527
|
+
return results.map((row) => {
|
|
2528
|
+
let columns = [];
|
|
2529
|
+
if (typeof row.columns === "string" && row.columns.startsWith("{") && row.columns.endsWith("}")) {
|
|
2530
|
+
const arrayContent = row.columns.slice(1, -1);
|
|
2531
|
+
columns = arrayContent ? arrayContent.split(",") : [];
|
|
2532
|
+
} else if (Array.isArray(row.columns)) {
|
|
2533
|
+
columns = row.columns;
|
|
2534
|
+
}
|
|
2535
|
+
return {
|
|
2536
|
+
name: row.name,
|
|
2537
|
+
table: row.table,
|
|
2538
|
+
columns,
|
|
2539
|
+
unique: row.is_unique || false,
|
|
2540
|
+
size: row.size || "0",
|
|
2541
|
+
definition: row.definition || ""
|
|
2542
|
+
};
|
|
2543
|
+
});
|
|
2544
|
+
} catch (error$1) {
|
|
2545
|
+
throw new error.MastraError(
|
|
2546
|
+
{
|
|
2547
|
+
id: "MASTRA_STORAGE_PG_INDEX_LIST_FAILED",
|
|
2548
|
+
domain: error.ErrorDomain.STORAGE,
|
|
2549
|
+
category: error.ErrorCategory.THIRD_PARTY,
|
|
2550
|
+
details: tableName ? {
|
|
2551
|
+
tableName
|
|
2552
|
+
} : {}
|
|
2553
|
+
},
|
|
2554
|
+
error$1
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
/**
|
|
2559
|
+
* Creates automatic indexes for optimal query performance
|
|
2560
|
+
* These composite indexes cover both filtering and sorting in single index
|
|
2561
|
+
*/
|
|
2562
|
+
async createAutomaticIndexes() {
|
|
2563
|
+
try {
|
|
2564
|
+
const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
|
|
2565
|
+
const indexes = [
|
|
2566
|
+
// Composite index for threads (filter + sort)
|
|
2567
|
+
{
|
|
2568
|
+
name: `${schemaPrefix}mastra_threads_resourceid_createdat_idx`,
|
|
2569
|
+
table: storage.TABLE_THREADS,
|
|
2570
|
+
columns: ["resourceId", "createdAt DESC"]
|
|
2571
|
+
},
|
|
2572
|
+
// Composite index for messages (filter + sort)
|
|
2573
|
+
{
|
|
2574
|
+
name: `${schemaPrefix}mastra_messages_thread_id_createdat_idx`,
|
|
2575
|
+
table: storage.TABLE_MESSAGES,
|
|
2576
|
+
columns: ["thread_id", "createdAt DESC"]
|
|
2577
|
+
},
|
|
2578
|
+
// Composite index for traces (filter + sort)
|
|
2579
|
+
{
|
|
2580
|
+
name: `${schemaPrefix}mastra_traces_name_starttime_idx`,
|
|
2581
|
+
table: storage.TABLE_TRACES,
|
|
2582
|
+
columns: ["name", "startTime DESC"]
|
|
2583
|
+
},
|
|
2584
|
+
// Composite index for evals (filter + sort)
|
|
2585
|
+
{
|
|
2586
|
+
name: `${schemaPrefix}mastra_evals_agent_name_created_at_idx`,
|
|
2587
|
+
table: storage.TABLE_EVALS,
|
|
2588
|
+
columns: ["agent_name", "created_at DESC"]
|
|
2589
|
+
}
|
|
2590
|
+
];
|
|
2591
|
+
for (const indexOptions of indexes) {
|
|
2592
|
+
try {
|
|
2593
|
+
await this.createIndex(indexOptions);
|
|
2594
|
+
} catch (error) {
|
|
2595
|
+
this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
} catch (error$1) {
|
|
2599
|
+
throw new error.MastraError(
|
|
2600
|
+
{
|
|
2601
|
+
id: "MASTRA_STORAGE_PG_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
|
|
2602
|
+
domain: error.ErrorDomain.STORAGE,
|
|
2603
|
+
category: error.ErrorCategory.THIRD_PARTY
|
|
2604
|
+
},
|
|
2605
|
+
error$1
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
/**
|
|
2610
|
+
* Get detailed statistics for a specific index
|
|
2611
|
+
*/
|
|
2612
|
+
async describeIndex(indexName) {
|
|
2613
|
+
try {
|
|
2614
|
+
const schemaName = this.schemaName || "public";
|
|
2615
|
+
const query = `
|
|
2616
|
+
SELECT
|
|
2617
|
+
i.indexname as name,
|
|
2618
|
+
i.tablename as table,
|
|
2619
|
+
i.indexdef as definition,
|
|
2620
|
+
ix.indisunique as is_unique,
|
|
2621
|
+
pg_size_pretty(pg_relation_size(c.oid)) as size,
|
|
2622
|
+
array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns,
|
|
2623
|
+
am.amname as method,
|
|
2624
|
+
s.idx_scan as scans,
|
|
2625
|
+
s.idx_tup_read as tuples_read,
|
|
2626
|
+
s.idx_tup_fetch as tuples_fetched
|
|
2627
|
+
FROM pg_indexes i
|
|
2628
|
+
JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
|
|
2629
|
+
JOIN pg_index ix ON ix.indexrelid = c.oid
|
|
2630
|
+
JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
|
|
2631
|
+
JOIN pg_am am ON c.relam = am.oid
|
|
2632
|
+
LEFT JOIN pg_stat_user_indexes s ON s.indexrelname = i.indexname AND s.schemaname = i.schemaname
|
|
2633
|
+
WHERE i.schemaname = $1
|
|
2634
|
+
AND i.indexname = $2
|
|
2635
|
+
GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid, am.amname, s.idx_scan, s.idx_tup_read, s.idx_tup_fetch
|
|
2636
|
+
`;
|
|
2637
|
+
const result = await this.client.oneOrNone(query, [schemaName, indexName]);
|
|
2638
|
+
if (!result) {
|
|
2639
|
+
throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
|
|
2640
|
+
}
|
|
2641
|
+
let columns = [];
|
|
2642
|
+
if (typeof result.columns === "string" && result.columns.startsWith("{") && result.columns.endsWith("}")) {
|
|
2643
|
+
const arrayContent = result.columns.slice(1, -1);
|
|
2644
|
+
columns = arrayContent ? arrayContent.split(",") : [];
|
|
2645
|
+
} else if (Array.isArray(result.columns)) {
|
|
2646
|
+
columns = result.columns;
|
|
2647
|
+
}
|
|
2648
|
+
return {
|
|
2649
|
+
name: result.name,
|
|
2650
|
+
table: result.table,
|
|
2651
|
+
columns,
|
|
2652
|
+
unique: result.is_unique || false,
|
|
2653
|
+
size: result.size || "0",
|
|
2654
|
+
definition: result.definition || "",
|
|
2655
|
+
method: result.method || "btree",
|
|
2656
|
+
scans: parseInt(result.scans) || 0,
|
|
2657
|
+
tuples_read: parseInt(result.tuples_read) || 0,
|
|
2658
|
+
tuples_fetched: parseInt(result.tuples_fetched) || 0
|
|
2659
|
+
};
|
|
2660
|
+
} catch (error$1) {
|
|
2661
|
+
throw new error.MastraError(
|
|
2662
|
+
{
|
|
2663
|
+
id: "MASTRA_STORAGE_PG_INDEX_DESCRIBE_FAILED",
|
|
2664
|
+
domain: error.ErrorDomain.STORAGE,
|
|
2665
|
+
category: error.ErrorCategory.THIRD_PARTY,
|
|
2666
|
+
details: {
|
|
2667
|
+
indexName
|
|
2668
|
+
}
|
|
2669
|
+
},
|
|
2670
|
+
error$1
|
|
2671
|
+
);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2377
2674
|
};
|
|
2378
2675
|
function transformScoreRow(row) {
|
|
2379
2676
|
return {
|
|
@@ -2808,16 +3105,17 @@ var WorkflowsPG = class extends storage.WorkflowsStorage {
|
|
|
2808
3105
|
async persistWorkflowSnapshot({
|
|
2809
3106
|
workflowName,
|
|
2810
3107
|
runId,
|
|
3108
|
+
resourceId,
|
|
2811
3109
|
snapshot
|
|
2812
3110
|
}) {
|
|
2813
3111
|
try {
|
|
2814
3112
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2815
3113
|
await this.client.none(
|
|
2816
|
-
`INSERT INTO ${getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: this.schema })} (workflow_name, run_id, snapshot, "createdAt", "updatedAt")
|
|
2817
|
-
VALUES ($1, $2, $3, $4, $5)
|
|
3114
|
+
`INSERT INTO ${getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: this.schema })} (workflow_name, run_id, "resourceId", snapshot, "createdAt", "updatedAt")
|
|
3115
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
2818
3116
|
ON CONFLICT (workflow_name, run_id) DO UPDATE
|
|
2819
|
-
SET
|
|
2820
|
-
[workflowName, runId, JSON.stringify(snapshot), now, now]
|
|
3117
|
+
SET "resourceId" = $3, snapshot = $4, "updatedAt" = $6`,
|
|
3118
|
+
[workflowName, runId, resourceId, JSON.stringify(snapshot), now, now]
|
|
2821
3119
|
);
|
|
2822
3120
|
} catch (error$1) {
|
|
2823
3121
|
throw new error.MastraError(
|
|
@@ -2979,37 +3277,68 @@ var PostgresStore = class extends storage.MastraStorage {
|
|
|
2979
3277
|
isConnected = false;
|
|
2980
3278
|
stores;
|
|
2981
3279
|
constructor(config) {
|
|
3280
|
+
const isConnectionStringConfig = (cfg) => {
|
|
3281
|
+
return "connectionString" in cfg;
|
|
3282
|
+
};
|
|
3283
|
+
const isHostConfig = (cfg) => {
|
|
3284
|
+
return "host" in cfg && "database" in cfg && "user" in cfg && "password" in cfg;
|
|
3285
|
+
};
|
|
3286
|
+
const isCloudSqlConfig = (cfg) => {
|
|
3287
|
+
return "stream" in cfg || "password" in cfg && typeof cfg.password === "function";
|
|
3288
|
+
};
|
|
2982
3289
|
try {
|
|
2983
|
-
if (
|
|
3290
|
+
if (isConnectionStringConfig(config)) {
|
|
2984
3291
|
if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
|
|
2985
3292
|
throw new Error(
|
|
2986
3293
|
"PostgresStore: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults."
|
|
2987
3294
|
);
|
|
2988
3295
|
}
|
|
2989
|
-
} else {
|
|
3296
|
+
} else if (isCloudSqlConfig(config)) ; else if (isHostConfig(config)) {
|
|
2990
3297
|
const required = ["host", "database", "user", "password"];
|
|
2991
3298
|
for (const key of required) {
|
|
2992
|
-
if (!
|
|
3299
|
+
if (!config[key] || typeof config[key] !== "string" || config[key].trim() === "") {
|
|
2993
3300
|
throw new Error(
|
|
2994
3301
|
`PostgresStore: ${key} must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.`
|
|
2995
3302
|
);
|
|
2996
3303
|
}
|
|
2997
3304
|
}
|
|
3305
|
+
} else {
|
|
3306
|
+
throw new Error(
|
|
3307
|
+
"PostgresStore: invalid config. Provide either {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with `stream`)."
|
|
3308
|
+
);
|
|
2998
3309
|
}
|
|
2999
3310
|
super({ name: "PostgresStore" });
|
|
3000
3311
|
this.schema = config.schemaName || "public";
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3312
|
+
if (isConnectionStringConfig(config)) {
|
|
3313
|
+
this.#config = {
|
|
3314
|
+
connectionString: config.connectionString,
|
|
3315
|
+
max: config.max,
|
|
3316
|
+
idleTimeoutMillis: config.idleTimeoutMillis
|
|
3317
|
+
};
|
|
3318
|
+
} else if (isCloudSqlConfig(config)) {
|
|
3319
|
+
this.#config = {
|
|
3320
|
+
...config,
|
|
3321
|
+
max: config.max,
|
|
3322
|
+
idleTimeoutMillis: config.idleTimeoutMillis
|
|
3323
|
+
};
|
|
3324
|
+
} else if (isHostConfig(config)) {
|
|
3325
|
+
this.#config = {
|
|
3005
3326
|
host: config.host,
|
|
3006
3327
|
port: config.port,
|
|
3007
3328
|
database: config.database,
|
|
3008
3329
|
user: config.user,
|
|
3009
3330
|
password: config.password,
|
|
3010
|
-
ssl: config.ssl
|
|
3011
|
-
|
|
3012
|
-
|
|
3331
|
+
ssl: config.ssl,
|
|
3332
|
+
max: config.max,
|
|
3333
|
+
idleTimeoutMillis: config.idleTimeoutMillis
|
|
3334
|
+
};
|
|
3335
|
+
} else {
|
|
3336
|
+
this.#config = {
|
|
3337
|
+
...config,
|
|
3338
|
+
max: config.max,
|
|
3339
|
+
idleTimeoutMillis: config.idleTimeoutMillis
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3013
3342
|
this.stores = {};
|
|
3014
3343
|
} catch (e) {
|
|
3015
3344
|
throw new error.MastraError(
|
|
@@ -3045,6 +3374,11 @@ var PostgresStore = class extends storage.MastraStorage {
|
|
|
3045
3374
|
memory
|
|
3046
3375
|
};
|
|
3047
3376
|
await super.init();
|
|
3377
|
+
try {
|
|
3378
|
+
await operations.createAutomaticIndexes();
|
|
3379
|
+
} catch (indexError) {
|
|
3380
|
+
console.warn("Failed to create indexes:", indexError);
|
|
3381
|
+
}
|
|
3048
3382
|
} catch (error$1) {
|
|
3049
3383
|
this.isConnected = false;
|
|
3050
3384
|
throw new error.MastraError(
|
|
@@ -3075,7 +3409,9 @@ var PostgresStore = class extends storage.MastraStorage {
|
|
|
3075
3409
|
resourceWorkingMemory: true,
|
|
3076
3410
|
hasColumn: true,
|
|
3077
3411
|
createTable: true,
|
|
3078
|
-
deleteMessages: true
|
|
3412
|
+
deleteMessages: true,
|
|
3413
|
+
aiTracing: false,
|
|
3414
|
+
indexManagement: true
|
|
3079
3415
|
};
|
|
3080
3416
|
}
|
|
3081
3417
|
/** @deprecated use getEvals instead */
|
|
@@ -3211,9 +3547,10 @@ var PostgresStore = class extends storage.MastraStorage {
|
|
|
3211
3547
|
async persistWorkflowSnapshot({
|
|
3212
3548
|
workflowName,
|
|
3213
3549
|
runId,
|
|
3550
|
+
resourceId,
|
|
3214
3551
|
snapshot
|
|
3215
3552
|
}) {
|
|
3216
|
-
return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
|
|
3553
|
+
return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
|
|
3217
3554
|
}
|
|
3218
3555
|
async loadWorkflowSnapshot({
|
|
3219
3556
|
workflowName,
|