@mastra/pg 0.15.3-alpha.0 → 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 +9 -0
- package/README.md +123 -0
- package/dist/index.cjs +305 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +306 -2
- 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/index.d.ts +3 -0
- 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/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Mutex } from 'async-mutex';
|
|
|
5
5
|
import pg from 'pg';
|
|
6
6
|
import xxhash from 'xxhash-wasm';
|
|
7
7
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
8
|
-
import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, ScoresStorage, TABLE_SCORERS, TracesStorage, safelyParseJSON,
|
|
8
|
+
import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS, TABLE_MESSAGES, TABLE_TRACES, TABLE_EVALS, ScoresStorage, TABLE_SCORERS, TracesStorage, safelyParseJSON, WorkflowsStorage, LegacyEvalsStorage, MemoryStorage, resolveMessageLimit, TABLE_RESOURCES } from '@mastra/core/storage';
|
|
9
9
|
import pgPromise from 'pg-promise';
|
|
10
10
|
import { MessageList } from '@mastra/core/agent';
|
|
11
11
|
|
|
@@ -2366,6 +2366,303 @@ var StoreOperationsPG = class extends StoreOperations {
|
|
|
2366
2366
|
);
|
|
2367
2367
|
}
|
|
2368
2368
|
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Create a new index on a table
|
|
2371
|
+
*/
|
|
2372
|
+
async createIndex(options) {
|
|
2373
|
+
try {
|
|
2374
|
+
const {
|
|
2375
|
+
name,
|
|
2376
|
+
table,
|
|
2377
|
+
columns,
|
|
2378
|
+
unique = false,
|
|
2379
|
+
concurrent = true,
|
|
2380
|
+
where,
|
|
2381
|
+
method = "btree",
|
|
2382
|
+
opclass,
|
|
2383
|
+
storage,
|
|
2384
|
+
tablespace
|
|
2385
|
+
} = options;
|
|
2386
|
+
const schemaName = this.schemaName || "public";
|
|
2387
|
+
const fullTableName = getTableName({
|
|
2388
|
+
indexName: table,
|
|
2389
|
+
schemaName: getSchemaName(this.schemaName)
|
|
2390
|
+
});
|
|
2391
|
+
const indexExists = await this.client.oneOrNone(
|
|
2392
|
+
`SELECT 1 FROM pg_indexes
|
|
2393
|
+
WHERE indexname = $1
|
|
2394
|
+
AND schemaname = $2`,
|
|
2395
|
+
[name, schemaName]
|
|
2396
|
+
);
|
|
2397
|
+
if (indexExists) {
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
const uniqueStr = unique ? "UNIQUE " : "";
|
|
2401
|
+
const concurrentStr = concurrent ? "CONCURRENTLY " : "";
|
|
2402
|
+
const methodStr = method !== "btree" ? `USING ${method} ` : "";
|
|
2403
|
+
const columnsStr = columns.map((col) => {
|
|
2404
|
+
if (col.includes(" DESC") || col.includes(" ASC")) {
|
|
2405
|
+
const [colName, ...modifiers] = col.split(" ");
|
|
2406
|
+
if (!colName) {
|
|
2407
|
+
throw new Error(`Invalid column specification: ${col}`);
|
|
2408
|
+
}
|
|
2409
|
+
const quotedCol2 = `"${parseSqlIdentifier(colName, "column name")}" ${modifiers.join(" ")}`;
|
|
2410
|
+
return opclass ? `${quotedCol2} ${opclass}` : quotedCol2;
|
|
2411
|
+
}
|
|
2412
|
+
const quotedCol = `"${parseSqlIdentifier(col, "column name")}"`;
|
|
2413
|
+
return opclass ? `${quotedCol} ${opclass}` : quotedCol;
|
|
2414
|
+
}).join(", ");
|
|
2415
|
+
const whereStr = where ? ` WHERE ${where}` : "";
|
|
2416
|
+
const tablespaceStr = tablespace ? ` TABLESPACE ${tablespace}` : "";
|
|
2417
|
+
let withStr = "";
|
|
2418
|
+
if (storage && Object.keys(storage).length > 0) {
|
|
2419
|
+
const storageParams = Object.entries(storage).map(([key, value]) => `${key} = ${value}`).join(", ");
|
|
2420
|
+
withStr = ` WITH (${storageParams})`;
|
|
2421
|
+
}
|
|
2422
|
+
const sql = `CREATE ${uniqueStr}INDEX ${concurrentStr}${name} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
|
|
2423
|
+
await this.client.none(sql);
|
|
2424
|
+
} catch (error) {
|
|
2425
|
+
if (error instanceof Error && error.message.includes("CONCURRENTLY")) {
|
|
2426
|
+
const retryOptions = { ...options, concurrent: false };
|
|
2427
|
+
return this.createIndex(retryOptions);
|
|
2428
|
+
}
|
|
2429
|
+
throw new MastraError(
|
|
2430
|
+
{
|
|
2431
|
+
id: "MASTRA_STORAGE_PG_INDEX_CREATE_FAILED",
|
|
2432
|
+
domain: ErrorDomain.STORAGE,
|
|
2433
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2434
|
+
details: {
|
|
2435
|
+
indexName: options.name,
|
|
2436
|
+
tableName: options.table
|
|
2437
|
+
}
|
|
2438
|
+
},
|
|
2439
|
+
error
|
|
2440
|
+
);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Drop an existing index
|
|
2445
|
+
*/
|
|
2446
|
+
async dropIndex(indexName) {
|
|
2447
|
+
try {
|
|
2448
|
+
const schemaName = this.schemaName || "public";
|
|
2449
|
+
const indexExists = await this.client.oneOrNone(
|
|
2450
|
+
`SELECT 1 FROM pg_indexes
|
|
2451
|
+
WHERE indexname = $1
|
|
2452
|
+
AND schemaname = $2`,
|
|
2453
|
+
[indexName, schemaName]
|
|
2454
|
+
);
|
|
2455
|
+
if (!indexExists) {
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
const sql = `DROP INDEX IF EXISTS ${getSchemaName(this.schemaName)}.${indexName}`;
|
|
2459
|
+
await this.client.none(sql);
|
|
2460
|
+
} catch (error) {
|
|
2461
|
+
throw new MastraError(
|
|
2462
|
+
{
|
|
2463
|
+
id: "MASTRA_STORAGE_PG_INDEX_DROP_FAILED",
|
|
2464
|
+
domain: ErrorDomain.STORAGE,
|
|
2465
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2466
|
+
details: {
|
|
2467
|
+
indexName
|
|
2468
|
+
}
|
|
2469
|
+
},
|
|
2470
|
+
error
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* List indexes for a specific table or all tables
|
|
2476
|
+
*/
|
|
2477
|
+
async listIndexes(tableName) {
|
|
2478
|
+
try {
|
|
2479
|
+
const schemaName = this.schemaName || "public";
|
|
2480
|
+
let query;
|
|
2481
|
+
let params;
|
|
2482
|
+
if (tableName) {
|
|
2483
|
+
query = `
|
|
2484
|
+
SELECT
|
|
2485
|
+
i.indexname as name,
|
|
2486
|
+
i.tablename as table,
|
|
2487
|
+
i.indexdef as definition,
|
|
2488
|
+
ix.indisunique as is_unique,
|
|
2489
|
+
pg_size_pretty(pg_relation_size(c.oid)) as size,
|
|
2490
|
+
array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
|
|
2491
|
+
FROM pg_indexes i
|
|
2492
|
+
JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
|
|
2493
|
+
JOIN pg_index ix ON ix.indexrelid = c.oid
|
|
2494
|
+
JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
|
|
2495
|
+
WHERE i.schemaname = $1
|
|
2496
|
+
AND i.tablename = $2
|
|
2497
|
+
GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
|
|
2498
|
+
`;
|
|
2499
|
+
params = [schemaName, tableName];
|
|
2500
|
+
} else {
|
|
2501
|
+
query = `
|
|
2502
|
+
SELECT
|
|
2503
|
+
i.indexname as name,
|
|
2504
|
+
i.tablename as table,
|
|
2505
|
+
i.indexdef as definition,
|
|
2506
|
+
ix.indisunique as is_unique,
|
|
2507
|
+
pg_size_pretty(pg_relation_size(c.oid)) as size,
|
|
2508
|
+
array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
|
|
2509
|
+
FROM pg_indexes i
|
|
2510
|
+
JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
|
|
2511
|
+
JOIN pg_index ix ON ix.indexrelid = c.oid
|
|
2512
|
+
JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
|
|
2513
|
+
WHERE i.schemaname = $1
|
|
2514
|
+
GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
|
|
2515
|
+
`;
|
|
2516
|
+
params = [schemaName];
|
|
2517
|
+
}
|
|
2518
|
+
const results = await this.client.manyOrNone(query, params);
|
|
2519
|
+
return results.map((row) => {
|
|
2520
|
+
let columns = [];
|
|
2521
|
+
if (typeof row.columns === "string" && row.columns.startsWith("{") && row.columns.endsWith("}")) {
|
|
2522
|
+
const arrayContent = row.columns.slice(1, -1);
|
|
2523
|
+
columns = arrayContent ? arrayContent.split(",") : [];
|
|
2524
|
+
} else if (Array.isArray(row.columns)) {
|
|
2525
|
+
columns = row.columns;
|
|
2526
|
+
}
|
|
2527
|
+
return {
|
|
2528
|
+
name: row.name,
|
|
2529
|
+
table: row.table,
|
|
2530
|
+
columns,
|
|
2531
|
+
unique: row.is_unique || false,
|
|
2532
|
+
size: row.size || "0",
|
|
2533
|
+
definition: row.definition || ""
|
|
2534
|
+
};
|
|
2535
|
+
});
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
throw new MastraError(
|
|
2538
|
+
{
|
|
2539
|
+
id: "MASTRA_STORAGE_PG_INDEX_LIST_FAILED",
|
|
2540
|
+
domain: ErrorDomain.STORAGE,
|
|
2541
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2542
|
+
details: tableName ? {
|
|
2543
|
+
tableName
|
|
2544
|
+
} : {}
|
|
2545
|
+
},
|
|
2546
|
+
error
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Creates automatic indexes for optimal query performance
|
|
2552
|
+
* These composite indexes cover both filtering and sorting in single index
|
|
2553
|
+
*/
|
|
2554
|
+
async createAutomaticIndexes() {
|
|
2555
|
+
try {
|
|
2556
|
+
const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
|
|
2557
|
+
const indexes = [
|
|
2558
|
+
// Composite index for threads (filter + sort)
|
|
2559
|
+
{
|
|
2560
|
+
name: `${schemaPrefix}mastra_threads_resourceid_createdat_idx`,
|
|
2561
|
+
table: TABLE_THREADS,
|
|
2562
|
+
columns: ["resourceId", "createdAt DESC"]
|
|
2563
|
+
},
|
|
2564
|
+
// Composite index for messages (filter + sort)
|
|
2565
|
+
{
|
|
2566
|
+
name: `${schemaPrefix}mastra_messages_thread_id_createdat_idx`,
|
|
2567
|
+
table: TABLE_MESSAGES,
|
|
2568
|
+
columns: ["thread_id", "createdAt DESC"]
|
|
2569
|
+
},
|
|
2570
|
+
// Composite index for traces (filter + sort)
|
|
2571
|
+
{
|
|
2572
|
+
name: `${schemaPrefix}mastra_traces_name_starttime_idx`,
|
|
2573
|
+
table: TABLE_TRACES,
|
|
2574
|
+
columns: ["name", "startTime DESC"]
|
|
2575
|
+
},
|
|
2576
|
+
// Composite index for evals (filter + sort)
|
|
2577
|
+
{
|
|
2578
|
+
name: `${schemaPrefix}mastra_evals_agent_name_created_at_idx`,
|
|
2579
|
+
table: TABLE_EVALS,
|
|
2580
|
+
columns: ["agent_name", "created_at DESC"]
|
|
2581
|
+
}
|
|
2582
|
+
];
|
|
2583
|
+
for (const indexOptions of indexes) {
|
|
2584
|
+
try {
|
|
2585
|
+
await this.createIndex(indexOptions);
|
|
2586
|
+
} catch (error) {
|
|
2587
|
+
this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
throw new MastraError(
|
|
2592
|
+
{
|
|
2593
|
+
id: "MASTRA_STORAGE_PG_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
|
|
2594
|
+
domain: ErrorDomain.STORAGE,
|
|
2595
|
+
category: ErrorCategory.THIRD_PARTY
|
|
2596
|
+
},
|
|
2597
|
+
error
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
/**
|
|
2602
|
+
* Get detailed statistics for a specific index
|
|
2603
|
+
*/
|
|
2604
|
+
async describeIndex(indexName) {
|
|
2605
|
+
try {
|
|
2606
|
+
const schemaName = this.schemaName || "public";
|
|
2607
|
+
const query = `
|
|
2608
|
+
SELECT
|
|
2609
|
+
i.indexname as name,
|
|
2610
|
+
i.tablename as table,
|
|
2611
|
+
i.indexdef as definition,
|
|
2612
|
+
ix.indisunique as is_unique,
|
|
2613
|
+
pg_size_pretty(pg_relation_size(c.oid)) as size,
|
|
2614
|
+
array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns,
|
|
2615
|
+
am.amname as method,
|
|
2616
|
+
s.idx_scan as scans,
|
|
2617
|
+
s.idx_tup_read as tuples_read,
|
|
2618
|
+
s.idx_tup_fetch as tuples_fetched
|
|
2619
|
+
FROM pg_indexes i
|
|
2620
|
+
JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
|
|
2621
|
+
JOIN pg_index ix ON ix.indexrelid = c.oid
|
|
2622
|
+
JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
|
|
2623
|
+
JOIN pg_am am ON c.relam = am.oid
|
|
2624
|
+
LEFT JOIN pg_stat_user_indexes s ON s.indexrelname = i.indexname AND s.schemaname = i.schemaname
|
|
2625
|
+
WHERE i.schemaname = $1
|
|
2626
|
+
AND i.indexname = $2
|
|
2627
|
+
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
|
|
2628
|
+
`;
|
|
2629
|
+
const result = await this.client.oneOrNone(query, [schemaName, indexName]);
|
|
2630
|
+
if (!result) {
|
|
2631
|
+
throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
|
|
2632
|
+
}
|
|
2633
|
+
let columns = [];
|
|
2634
|
+
if (typeof result.columns === "string" && result.columns.startsWith("{") && result.columns.endsWith("}")) {
|
|
2635
|
+
const arrayContent = result.columns.slice(1, -1);
|
|
2636
|
+
columns = arrayContent ? arrayContent.split(",") : [];
|
|
2637
|
+
} else if (Array.isArray(result.columns)) {
|
|
2638
|
+
columns = result.columns;
|
|
2639
|
+
}
|
|
2640
|
+
return {
|
|
2641
|
+
name: result.name,
|
|
2642
|
+
table: result.table,
|
|
2643
|
+
columns,
|
|
2644
|
+
unique: result.is_unique || false,
|
|
2645
|
+
size: result.size || "0",
|
|
2646
|
+
definition: result.definition || "",
|
|
2647
|
+
method: result.method || "btree",
|
|
2648
|
+
scans: parseInt(result.scans) || 0,
|
|
2649
|
+
tuples_read: parseInt(result.tuples_read) || 0,
|
|
2650
|
+
tuples_fetched: parseInt(result.tuples_fetched) || 0
|
|
2651
|
+
};
|
|
2652
|
+
} catch (error) {
|
|
2653
|
+
throw new MastraError(
|
|
2654
|
+
{
|
|
2655
|
+
id: "MASTRA_STORAGE_PG_INDEX_DESCRIBE_FAILED",
|
|
2656
|
+
domain: ErrorDomain.STORAGE,
|
|
2657
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2658
|
+
details: {
|
|
2659
|
+
indexName
|
|
2660
|
+
}
|
|
2661
|
+
},
|
|
2662
|
+
error
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2369
2666
|
};
|
|
2370
2667
|
function transformScoreRow(row) {
|
|
2371
2668
|
return {
|
|
@@ -3069,6 +3366,11 @@ var PostgresStore = class extends MastraStorage {
|
|
|
3069
3366
|
memory
|
|
3070
3367
|
};
|
|
3071
3368
|
await super.init();
|
|
3369
|
+
try {
|
|
3370
|
+
await operations.createAutomaticIndexes();
|
|
3371
|
+
} catch (indexError) {
|
|
3372
|
+
console.warn("Failed to create indexes:", indexError);
|
|
3373
|
+
}
|
|
3072
3374
|
} catch (error) {
|
|
3073
3375
|
this.isConnected = false;
|
|
3074
3376
|
throw new MastraError(
|
|
@@ -3099,7 +3401,9 @@ var PostgresStore = class extends MastraStorage {
|
|
|
3099
3401
|
resourceWorkingMemory: true,
|
|
3100
3402
|
hasColumn: true,
|
|
3101
3403
|
createTable: true,
|
|
3102
|
-
deleteMessages: true
|
|
3404
|
+
deleteMessages: true,
|
|
3405
|
+
aiTracing: false,
|
|
3406
|
+
indexManagement: true
|
|
3103
3407
|
};
|
|
3104
3408
|
}
|
|
3105
3409
|
/** @deprecated use getEvals instead */
|