@mastra/pg 0.2.7-alpha.4 → 0.2.7-alpha.6
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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +16 -0
- package/dist/_tsup-dts-rollup.d.cts +7 -1
- package/dist/_tsup-dts-rollup.d.ts +7 -1
- package/dist/index.cjs +95 -90
- package/dist/index.js +94 -90
- package/package.json +4 -2
- package/src/storage/index.ts +4 -9
- package/src/vector/index.ts +102 -105
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/pg@0.2.7-alpha.
|
|
2
|
+
> @mastra/pg@0.2.7-alpha.6 build /home/runner/work/mastra/mastra/stores/pg
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.4.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 10263ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.8.2
|
|
13
13
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/pg/dist/_tsup-dts-rollup.d.ts[39m
|
|
14
14
|
Analysis will use the bundled TypeScript version 5.8.2
|
|
15
15
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/pg/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 11464ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[32mCJS[39m [1mdist/index.cjs [22m[32m40.
|
|
21
|
-
[32mCJS[39m ⚡️ Build success in
|
|
22
|
-
[32mESM[39m [1mdist/index.js [22m[32m40.
|
|
23
|
-
[32mESM[39m ⚡️ Build success in
|
|
20
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m40.86 KB[39m
|
|
21
|
+
[32mCJS[39m ⚡️ Build success in 1298ms
|
|
22
|
+
[32mESM[39m [1mdist/index.js [22m[32m40.44 KB[39m
|
|
23
|
+
[32mESM[39m ⚡️ Build success in 1321ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @mastra/pg
|
|
2
2
|
|
|
3
|
+
## 0.2.7-alpha.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a3f0e90: Update storage initialization to ensure tables are present
|
|
8
|
+
- Updated dependencies [a3f0e90]
|
|
9
|
+
- @mastra/core@0.8.0-alpha.6
|
|
10
|
+
|
|
11
|
+
## 0.2.7-alpha.5
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 93875ed: Improved the performance of Memory semantic recall by 2 to 3 times when using pg by making tweaks to @mastra/memory @mastra/core and @mastra/pg
|
|
16
|
+
- Updated dependencies [93875ed]
|
|
17
|
+
- @mastra/core@0.8.0-alpha.5
|
|
18
|
+
|
|
3
19
|
## 0.2.7-alpha.4
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
@@ -200,12 +200,18 @@ declare interface PgQueryVectorParams extends QueryVectorParams {
|
|
|
200
200
|
|
|
201
201
|
declare class PgVector extends MastraVector {
|
|
202
202
|
private pool;
|
|
203
|
-
private
|
|
203
|
+
private describeIndexCache;
|
|
204
|
+
private createdIndexes;
|
|
205
|
+
private mutexesByName;
|
|
204
206
|
constructor(connectionString: string);
|
|
207
|
+
private getMutexByName;
|
|
205
208
|
transformFilter(filter?: VectorFilter): VectorFilter;
|
|
206
209
|
getIndexInfo(indexName: string): Promise<PGIndexStats>;
|
|
207
210
|
query(...args: ParamsToArgs<PgQueryVectorParams> | PgQueryVectorArgs): Promise<QueryResult[]>;
|
|
208
211
|
upsert(...args: ParamsToArgs<UpsertVectorParams>): Promise<string[]>;
|
|
212
|
+
private hasher;
|
|
213
|
+
private getIndexCacheKey;
|
|
214
|
+
private cachedIndexExists;
|
|
209
215
|
createIndex(...args: ParamsToArgs<PgCreateIndexParams> | PgCreateIndexArgs): Promise<void>;
|
|
210
216
|
/**
|
|
211
217
|
* @deprecated This function is deprecated. Use buildIndex instead
|
|
@@ -200,12 +200,18 @@ declare interface PgQueryVectorParams extends QueryVectorParams {
|
|
|
200
200
|
|
|
201
201
|
declare class PgVector extends MastraVector {
|
|
202
202
|
private pool;
|
|
203
|
-
private
|
|
203
|
+
private describeIndexCache;
|
|
204
|
+
private createdIndexes;
|
|
205
|
+
private mutexesByName;
|
|
204
206
|
constructor(connectionString: string);
|
|
207
|
+
private getMutexByName;
|
|
205
208
|
transformFilter(filter?: VectorFilter): VectorFilter;
|
|
206
209
|
getIndexInfo(indexName: string): Promise<PGIndexStats>;
|
|
207
210
|
query(...args: ParamsToArgs<PgQueryVectorParams> | PgQueryVectorArgs): Promise<QueryResult[]>;
|
|
208
211
|
upsert(...args: ParamsToArgs<UpsertVectorParams>): Promise<string[]>;
|
|
212
|
+
private hasher;
|
|
213
|
+
private getIndexCacheKey;
|
|
214
|
+
private cachedIndexExists;
|
|
209
215
|
createIndex(...args: ParamsToArgs<PgCreateIndexParams> | PgCreateIndexArgs): Promise<void>;
|
|
210
216
|
/**
|
|
211
217
|
* @deprecated This function is deprecated. Use buildIndex instead
|
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var crypto$1 = require('crypto');
|
|
4
3
|
var vector = require('@mastra/core/vector');
|
|
4
|
+
var asyncMutex = require('async-mutex');
|
|
5
5
|
var pg = require('pg');
|
|
6
|
+
var xxhash = require('xxhash-wasm');
|
|
6
7
|
var filter = require('@mastra/core/vector/filter');
|
|
7
8
|
var storage = require('@mastra/core/storage');
|
|
8
9
|
var pgPromise = require('pg-promise');
|
|
@@ -10,6 +11,7 @@ var pgPromise = require('pg-promise');
|
|
|
10
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
|
|
12
13
|
var pg__default = /*#__PURE__*/_interopDefault(pg);
|
|
14
|
+
var xxhash__default = /*#__PURE__*/_interopDefault(xxhash);
|
|
13
15
|
var pgPromise__default = /*#__PURE__*/_interopDefault(pgPromise);
|
|
14
16
|
|
|
15
17
|
// src/vector/index.ts
|
|
@@ -294,7 +296,9 @@ function buildFilterQuery(filter, minScore) {
|
|
|
294
296
|
// src/vector/index.ts
|
|
295
297
|
var PgVector = class extends vector.MastraVector {
|
|
296
298
|
pool;
|
|
297
|
-
|
|
299
|
+
describeIndexCache = /* @__PURE__ */ new Map();
|
|
300
|
+
createdIndexes = /* @__PURE__ */ new Map();
|
|
301
|
+
mutexesByName = /* @__PURE__ */ new Map();
|
|
298
302
|
constructor(connectionString) {
|
|
299
303
|
super();
|
|
300
304
|
const basePool = new pg__default.default.Pool({
|
|
@@ -313,16 +317,33 @@ var PgVector = class extends vector.MastraVector {
|
|
|
313
317
|
"vector.type": "postgres"
|
|
314
318
|
}
|
|
315
319
|
}) ?? basePool;
|
|
320
|
+
void (async () => {
|
|
321
|
+
const existingIndexes = await this.listIndexes();
|
|
322
|
+
void existingIndexes.map(async (indexName) => {
|
|
323
|
+
const info = await this.getIndexInfo(indexName);
|
|
324
|
+
const key = await this.getIndexCacheKey({
|
|
325
|
+
indexName,
|
|
326
|
+
metric: info.metric,
|
|
327
|
+
dimension: info.dimension,
|
|
328
|
+
type: info.type
|
|
329
|
+
});
|
|
330
|
+
this.createdIndexes.set(indexName, key);
|
|
331
|
+
});
|
|
332
|
+
})();
|
|
333
|
+
}
|
|
334
|
+
getMutexByName(indexName) {
|
|
335
|
+
if (!this.mutexesByName.has(indexName)) this.mutexesByName.set(indexName, new asyncMutex.Mutex());
|
|
336
|
+
return this.mutexesByName.get(indexName);
|
|
316
337
|
}
|
|
317
338
|
transformFilter(filter) {
|
|
318
339
|
const translator = new PGFilterTranslator();
|
|
319
340
|
return translator.translate(filter);
|
|
320
341
|
}
|
|
321
342
|
async getIndexInfo(indexName) {
|
|
322
|
-
if (!this.
|
|
323
|
-
this.
|
|
343
|
+
if (!this.describeIndexCache.has(indexName)) {
|
|
344
|
+
this.describeIndexCache.set(indexName, await this.describeIndex(indexName));
|
|
324
345
|
}
|
|
325
|
-
return this.
|
|
346
|
+
return this.describeIndexCache.get(indexName);
|
|
326
347
|
}
|
|
327
348
|
async query(...args) {
|
|
328
349
|
const params = this.normalizeArgs("query", args, [
|
|
@@ -408,69 +429,77 @@ var PgVector = class extends vector.MastraVector {
|
|
|
408
429
|
client.release();
|
|
409
430
|
}
|
|
410
431
|
}
|
|
432
|
+
hasher = xxhash__default.default();
|
|
433
|
+
async getIndexCacheKey(params) {
|
|
434
|
+
const input = params.indexName + params.dimension + params.metric + (params.type || "ivfflat");
|
|
435
|
+
return (await this.hasher).h32(input);
|
|
436
|
+
}
|
|
437
|
+
cachedIndexExists(indexName, newKey) {
|
|
438
|
+
const existingIndexCacheKey = this.createdIndexes.get(indexName);
|
|
439
|
+
return existingIndexCacheKey && existingIndexCacheKey === newKey;
|
|
440
|
+
}
|
|
411
441
|
async createIndex(...args) {
|
|
412
442
|
const params = this.normalizeArgs("createIndex", args, [
|
|
413
443
|
"indexConfig",
|
|
414
444
|
"buildIndex"
|
|
415
445
|
]);
|
|
416
446
|
const { indexName, dimension, metric = "cosine", indexConfig = {}, buildIndex = true } = params;
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
447
|
+
if (!indexName.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
|
|
448
|
+
throw new Error("Invalid index name format");
|
|
449
|
+
}
|
|
450
|
+
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
451
|
+
throw new Error("Dimension must be a positive integer");
|
|
452
|
+
}
|
|
453
|
+
const indexCacheKey = await this.getIndexCacheKey({ indexName, dimension, type: indexConfig.type, metric });
|
|
454
|
+
if (this.cachedIndexExists(indexName, indexCacheKey)) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const mutex = this.getMutexByName(`create-${indexName}`);
|
|
458
|
+
await mutex.runExclusive(async () => {
|
|
459
|
+
if (this.cachedIndexExists(indexName, indexCacheKey)) {
|
|
460
|
+
return;
|
|
424
461
|
}
|
|
425
|
-
const
|
|
462
|
+
const client = await this.pool.connect();
|
|
463
|
+
try {
|
|
464
|
+
const extensionCheck = await client.query(`
|
|
426
465
|
SELECT EXISTS (
|
|
427
466
|
SELECT 1 FROM pg_available_extensions WHERE name = 'vector'
|
|
428
467
|
);
|
|
429
468
|
`);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const hash = crypto$1.createHash("sha256").update(indexName).digest("hex");
|
|
434
|
-
const lockId = BigInt("0x" + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
435
|
-
const acquired = await client.query("SELECT pg_try_advisory_lock($1)", [lockId]);
|
|
436
|
-
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
437
|
-
const exists = await client.query(
|
|
438
|
-
`
|
|
439
|
-
SELECT 1 FROM pg_class c
|
|
440
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
441
|
-
WHERE c.relname = $1
|
|
442
|
-
AND n.nspname = 'public'
|
|
443
|
-
`,
|
|
444
|
-
[indexName]
|
|
445
|
-
);
|
|
446
|
-
if (exists.rows.length > 0) {
|
|
447
|
-
console.log(`Table ${indexName} already exists, skipping creation`);
|
|
448
|
-
return;
|
|
469
|
+
if (!extensionCheck.rows[0].exists) {
|
|
470
|
+
this.createdIndexes.delete(indexName);
|
|
471
|
+
throw new Error("PostgreSQL vector extension is not available. Please install it first.");
|
|
449
472
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
473
|
+
try {
|
|
474
|
+
await client.query(`
|
|
475
|
+
DO $$
|
|
476
|
+
BEGIN
|
|
477
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
478
|
+
|
|
479
|
+
CREATE TABLE IF NOT EXISTS ${indexName} (
|
|
480
|
+
id SERIAL PRIMARY KEY,
|
|
481
|
+
vector_id TEXT UNIQUE NOT NULL,
|
|
482
|
+
embedding vector(${dimension}),
|
|
483
|
+
metadata JSONB DEFAULT '{}'::jsonb
|
|
484
|
+
);
|
|
485
|
+
END $$;
|
|
486
|
+
`);
|
|
487
|
+
this.createdIndexes.set(indexName, indexCacheKey);
|
|
488
|
+
} catch (e) {
|
|
489
|
+
this.createdIndexes.delete(indexName);
|
|
490
|
+
throw e;
|
|
491
|
+
}
|
|
492
|
+
if (buildIndex) {
|
|
493
|
+
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
494
|
+
}
|
|
495
|
+
} catch (error) {
|
|
496
|
+
this.createdIndexes.delete(indexName);
|
|
497
|
+
console.error("Failed to create vector table:", error);
|
|
498
|
+
throw error;
|
|
462
499
|
} finally {
|
|
463
|
-
|
|
500
|
+
client.release();
|
|
464
501
|
}
|
|
465
|
-
|
|
466
|
-
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
467
|
-
}
|
|
468
|
-
} catch (error) {
|
|
469
|
-
console.error("Failed to create vector table:", error);
|
|
470
|
-
throw error;
|
|
471
|
-
} finally {
|
|
472
|
-
client.release();
|
|
473
|
-
}
|
|
502
|
+
});
|
|
474
503
|
}
|
|
475
504
|
/**
|
|
476
505
|
* @deprecated This function is deprecated. Use buildIndex instead
|
|
@@ -492,30 +521,13 @@ var PgVector = class extends vector.MastraVector {
|
|
|
492
521
|
}
|
|
493
522
|
}
|
|
494
523
|
async setupIndex({ indexName, metric, indexConfig }, client) {
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const exists = await client.query(
|
|
500
|
-
`
|
|
501
|
-
SELECT 1 FROM pg_class c
|
|
502
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
503
|
-
WHERE c.relname = $1
|
|
504
|
-
AND n.nspname = 'public'
|
|
505
|
-
`,
|
|
506
|
-
[`${indexName}_vector_idx`]
|
|
507
|
-
);
|
|
508
|
-
if (exists.rows.length > 0) {
|
|
509
|
-
console.log(`Index ${indexName}_vector_idx already exists, skipping creation`);
|
|
510
|
-
this.indexCache.delete(indexName);
|
|
511
|
-
return;
|
|
524
|
+
const mutex = this.getMutexByName(`build-${indexName}`);
|
|
525
|
+
await mutex.runExclusive(async () => {
|
|
526
|
+
if (this.createdIndexes.has(indexName)) {
|
|
527
|
+
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
512
528
|
}
|
|
513
|
-
await client.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
514
|
-
}
|
|
515
|
-
try {
|
|
516
|
-
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
517
529
|
if (indexConfig.type === "flat") {
|
|
518
|
-
this.
|
|
530
|
+
this.describeIndexCache.delete(indexName);
|
|
519
531
|
return;
|
|
520
532
|
}
|
|
521
533
|
const metricOp = metric === "cosine" ? "vector_cosine_ops" : metric === "euclidean" ? "vector_l2_ops" : "vector_ip_ops";
|
|
@@ -548,10 +560,7 @@ var PgVector = class extends vector.MastraVector {
|
|
|
548
560
|
`;
|
|
549
561
|
}
|
|
550
562
|
await client.query(indexSQL);
|
|
551
|
-
|
|
552
|
-
} finally {
|
|
553
|
-
await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
554
|
-
}
|
|
563
|
+
});
|
|
555
564
|
}
|
|
556
565
|
async listIndexes() {
|
|
557
566
|
const client = await this.pool.connect();
|
|
@@ -577,8 +586,7 @@ var PgVector = class extends vector.MastraVector {
|
|
|
577
586
|
WHERE attrelid = $1::regclass
|
|
578
587
|
AND attname = 'embedding';
|
|
579
588
|
`;
|
|
580
|
-
const countQuery = `
|
|
581
|
-
SELECT COUNT(*) as count
|
|
589
|
+
const countQuery = ` SELECT COUNT(*) as count
|
|
582
590
|
FROM ${indexName};
|
|
583
591
|
`;
|
|
584
592
|
const indexQuery = `
|
|
@@ -631,6 +639,7 @@ var PgVector = class extends vector.MastraVector {
|
|
|
631
639
|
const client = await this.pool.connect();
|
|
632
640
|
try {
|
|
633
641
|
await client.query(`DROP TABLE IF EXISTS ${indexName} CASCADE`);
|
|
642
|
+
this.createdIndexes.delete(indexName);
|
|
634
643
|
} catch (error) {
|
|
635
644
|
await client.query("ROLLBACK");
|
|
636
645
|
throw new Error(`Failed to delete vector table: ${error.message}`);
|
|
@@ -713,15 +722,16 @@ var PostgresStore = class extends storage.MastraStorage {
|
|
|
713
722
|
}
|
|
714
723
|
);
|
|
715
724
|
}
|
|
716
|
-
getEvalsByAgentName(agentName, type) {
|
|
725
|
+
async getEvalsByAgentName(agentName, type) {
|
|
717
726
|
try {
|
|
718
727
|
const baseQuery = `SELECT * FROM ${storage.TABLE_EVALS} WHERE agent_name = $1`;
|
|
719
728
|
const typeCondition = type === "test" ? " AND test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL" : type === "live" ? " AND (test_info IS NULL OR test_info->>'testPath' IS NULL)" : "";
|
|
720
729
|
const query = `${baseQuery}${typeCondition} ORDER BY created_at DESC`;
|
|
721
|
-
|
|
730
|
+
const rows = await this.db.manyOrNone(query, [agentName]);
|
|
731
|
+
return rows?.map((row) => this.transformEvalRow(row)) ?? [];
|
|
722
732
|
} catch (error) {
|
|
723
733
|
if (error instanceof Error && error.message.includes("relation") && error.message.includes("does not exist")) {
|
|
724
|
-
return
|
|
734
|
+
return [];
|
|
725
735
|
}
|
|
726
736
|
console.error("Failed to get evals for the specified agent: " + error?.message);
|
|
727
737
|
throw error;
|
|
@@ -808,11 +818,6 @@ var PostgresStore = class extends storage.MastraStorage {
|
|
|
808
818
|
args.push(value);
|
|
809
819
|
}
|
|
810
820
|
}
|
|
811
|
-
console.log(
|
|
812
|
-
"QUERY",
|
|
813
|
-
`SELECT * FROM ${storage.TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
|
|
814
|
-
args
|
|
815
|
-
);
|
|
816
821
|
const result = await this.db.manyOrNone(`SELECT * FROM ${storage.TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`, args);
|
|
817
822
|
if (!result) {
|
|
818
823
|
return [];
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
2
1
|
import { MastraVector } from '@mastra/core/vector';
|
|
2
|
+
import { Mutex } from 'async-mutex';
|
|
3
3
|
import pg from 'pg';
|
|
4
|
+
import xxhash from 'xxhash-wasm';
|
|
4
5
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
5
6
|
import { MastraStorage, TABLE_EVALS, TABLE_TRACES, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS, TABLE_MESSAGES } from '@mastra/core/storage';
|
|
6
7
|
import pgPromise from 'pg-promise';
|
|
@@ -287,7 +288,9 @@ function buildFilterQuery(filter, minScore) {
|
|
|
287
288
|
// src/vector/index.ts
|
|
288
289
|
var PgVector = class extends MastraVector {
|
|
289
290
|
pool;
|
|
290
|
-
|
|
291
|
+
describeIndexCache = /* @__PURE__ */ new Map();
|
|
292
|
+
createdIndexes = /* @__PURE__ */ new Map();
|
|
293
|
+
mutexesByName = /* @__PURE__ */ new Map();
|
|
291
294
|
constructor(connectionString) {
|
|
292
295
|
super();
|
|
293
296
|
const basePool = new pg.Pool({
|
|
@@ -306,16 +309,33 @@ var PgVector = class extends MastraVector {
|
|
|
306
309
|
"vector.type": "postgres"
|
|
307
310
|
}
|
|
308
311
|
}) ?? basePool;
|
|
312
|
+
void (async () => {
|
|
313
|
+
const existingIndexes = await this.listIndexes();
|
|
314
|
+
void existingIndexes.map(async (indexName) => {
|
|
315
|
+
const info = await this.getIndexInfo(indexName);
|
|
316
|
+
const key = await this.getIndexCacheKey({
|
|
317
|
+
indexName,
|
|
318
|
+
metric: info.metric,
|
|
319
|
+
dimension: info.dimension,
|
|
320
|
+
type: info.type
|
|
321
|
+
});
|
|
322
|
+
this.createdIndexes.set(indexName, key);
|
|
323
|
+
});
|
|
324
|
+
})();
|
|
325
|
+
}
|
|
326
|
+
getMutexByName(indexName) {
|
|
327
|
+
if (!this.mutexesByName.has(indexName)) this.mutexesByName.set(indexName, new Mutex());
|
|
328
|
+
return this.mutexesByName.get(indexName);
|
|
309
329
|
}
|
|
310
330
|
transformFilter(filter) {
|
|
311
331
|
const translator = new PGFilterTranslator();
|
|
312
332
|
return translator.translate(filter);
|
|
313
333
|
}
|
|
314
334
|
async getIndexInfo(indexName) {
|
|
315
|
-
if (!this.
|
|
316
|
-
this.
|
|
335
|
+
if (!this.describeIndexCache.has(indexName)) {
|
|
336
|
+
this.describeIndexCache.set(indexName, await this.describeIndex(indexName));
|
|
317
337
|
}
|
|
318
|
-
return this.
|
|
338
|
+
return this.describeIndexCache.get(indexName);
|
|
319
339
|
}
|
|
320
340
|
async query(...args) {
|
|
321
341
|
const params = this.normalizeArgs("query", args, [
|
|
@@ -401,69 +421,77 @@ var PgVector = class extends MastraVector {
|
|
|
401
421
|
client.release();
|
|
402
422
|
}
|
|
403
423
|
}
|
|
424
|
+
hasher = xxhash();
|
|
425
|
+
async getIndexCacheKey(params) {
|
|
426
|
+
const input = params.indexName + params.dimension + params.metric + (params.type || "ivfflat");
|
|
427
|
+
return (await this.hasher).h32(input);
|
|
428
|
+
}
|
|
429
|
+
cachedIndexExists(indexName, newKey) {
|
|
430
|
+
const existingIndexCacheKey = this.createdIndexes.get(indexName);
|
|
431
|
+
return existingIndexCacheKey && existingIndexCacheKey === newKey;
|
|
432
|
+
}
|
|
404
433
|
async createIndex(...args) {
|
|
405
434
|
const params = this.normalizeArgs("createIndex", args, [
|
|
406
435
|
"indexConfig",
|
|
407
436
|
"buildIndex"
|
|
408
437
|
]);
|
|
409
438
|
const { indexName, dimension, metric = "cosine", indexConfig = {}, buildIndex = true } = params;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
439
|
+
if (!indexName.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
|
|
440
|
+
throw new Error("Invalid index name format");
|
|
441
|
+
}
|
|
442
|
+
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
443
|
+
throw new Error("Dimension must be a positive integer");
|
|
444
|
+
}
|
|
445
|
+
const indexCacheKey = await this.getIndexCacheKey({ indexName, dimension, type: indexConfig.type, metric });
|
|
446
|
+
if (this.cachedIndexExists(indexName, indexCacheKey)) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const mutex = this.getMutexByName(`create-${indexName}`);
|
|
450
|
+
await mutex.runExclusive(async () => {
|
|
451
|
+
if (this.cachedIndexExists(indexName, indexCacheKey)) {
|
|
452
|
+
return;
|
|
417
453
|
}
|
|
418
|
-
const
|
|
454
|
+
const client = await this.pool.connect();
|
|
455
|
+
try {
|
|
456
|
+
const extensionCheck = await client.query(`
|
|
419
457
|
SELECT EXISTS (
|
|
420
458
|
SELECT 1 FROM pg_available_extensions WHERE name = 'vector'
|
|
421
459
|
);
|
|
422
460
|
`);
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const hash = createHash("sha256").update(indexName).digest("hex");
|
|
427
|
-
const lockId = BigInt("0x" + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
428
|
-
const acquired = await client.query("SELECT pg_try_advisory_lock($1)", [lockId]);
|
|
429
|
-
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
430
|
-
const exists = await client.query(
|
|
431
|
-
`
|
|
432
|
-
SELECT 1 FROM pg_class c
|
|
433
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
434
|
-
WHERE c.relname = $1
|
|
435
|
-
AND n.nspname = 'public'
|
|
436
|
-
`,
|
|
437
|
-
[indexName]
|
|
438
|
-
);
|
|
439
|
-
if (exists.rows.length > 0) {
|
|
440
|
-
console.log(`Table ${indexName} already exists, skipping creation`);
|
|
441
|
-
return;
|
|
461
|
+
if (!extensionCheck.rows[0].exists) {
|
|
462
|
+
this.createdIndexes.delete(indexName);
|
|
463
|
+
throw new Error("PostgreSQL vector extension is not available. Please install it first.");
|
|
442
464
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
465
|
+
try {
|
|
466
|
+
await client.query(`
|
|
467
|
+
DO $$
|
|
468
|
+
BEGIN
|
|
469
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
470
|
+
|
|
471
|
+
CREATE TABLE IF NOT EXISTS ${indexName} (
|
|
472
|
+
id SERIAL PRIMARY KEY,
|
|
473
|
+
vector_id TEXT UNIQUE NOT NULL,
|
|
474
|
+
embedding vector(${dimension}),
|
|
475
|
+
metadata JSONB DEFAULT '{}'::jsonb
|
|
476
|
+
);
|
|
477
|
+
END $$;
|
|
478
|
+
`);
|
|
479
|
+
this.createdIndexes.set(indexName, indexCacheKey);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
this.createdIndexes.delete(indexName);
|
|
482
|
+
throw e;
|
|
483
|
+
}
|
|
484
|
+
if (buildIndex) {
|
|
485
|
+
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
this.createdIndexes.delete(indexName);
|
|
489
|
+
console.error("Failed to create vector table:", error);
|
|
490
|
+
throw error;
|
|
455
491
|
} finally {
|
|
456
|
-
|
|
492
|
+
client.release();
|
|
457
493
|
}
|
|
458
|
-
|
|
459
|
-
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
460
|
-
}
|
|
461
|
-
} catch (error) {
|
|
462
|
-
console.error("Failed to create vector table:", error);
|
|
463
|
-
throw error;
|
|
464
|
-
} finally {
|
|
465
|
-
client.release();
|
|
466
|
-
}
|
|
494
|
+
});
|
|
467
495
|
}
|
|
468
496
|
/**
|
|
469
497
|
* @deprecated This function is deprecated. Use buildIndex instead
|
|
@@ -485,30 +513,13 @@ var PgVector = class extends MastraVector {
|
|
|
485
513
|
}
|
|
486
514
|
}
|
|
487
515
|
async setupIndex({ indexName, metric, indexConfig }, client) {
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const exists = await client.query(
|
|
493
|
-
`
|
|
494
|
-
SELECT 1 FROM pg_class c
|
|
495
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
496
|
-
WHERE c.relname = $1
|
|
497
|
-
AND n.nspname = 'public'
|
|
498
|
-
`,
|
|
499
|
-
[`${indexName}_vector_idx`]
|
|
500
|
-
);
|
|
501
|
-
if (exists.rows.length > 0) {
|
|
502
|
-
console.log(`Index ${indexName}_vector_idx already exists, skipping creation`);
|
|
503
|
-
this.indexCache.delete(indexName);
|
|
504
|
-
return;
|
|
516
|
+
const mutex = this.getMutexByName(`build-${indexName}`);
|
|
517
|
+
await mutex.runExclusive(async () => {
|
|
518
|
+
if (this.createdIndexes.has(indexName)) {
|
|
519
|
+
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
505
520
|
}
|
|
506
|
-
await client.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
507
|
-
}
|
|
508
|
-
try {
|
|
509
|
-
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
510
521
|
if (indexConfig.type === "flat") {
|
|
511
|
-
this.
|
|
522
|
+
this.describeIndexCache.delete(indexName);
|
|
512
523
|
return;
|
|
513
524
|
}
|
|
514
525
|
const metricOp = metric === "cosine" ? "vector_cosine_ops" : metric === "euclidean" ? "vector_l2_ops" : "vector_ip_ops";
|
|
@@ -541,10 +552,7 @@ var PgVector = class extends MastraVector {
|
|
|
541
552
|
`;
|
|
542
553
|
}
|
|
543
554
|
await client.query(indexSQL);
|
|
544
|
-
|
|
545
|
-
} finally {
|
|
546
|
-
await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
547
|
-
}
|
|
555
|
+
});
|
|
548
556
|
}
|
|
549
557
|
async listIndexes() {
|
|
550
558
|
const client = await this.pool.connect();
|
|
@@ -570,8 +578,7 @@ var PgVector = class extends MastraVector {
|
|
|
570
578
|
WHERE attrelid = $1::regclass
|
|
571
579
|
AND attname = 'embedding';
|
|
572
580
|
`;
|
|
573
|
-
const countQuery = `
|
|
574
|
-
SELECT COUNT(*) as count
|
|
581
|
+
const countQuery = ` SELECT COUNT(*) as count
|
|
575
582
|
FROM ${indexName};
|
|
576
583
|
`;
|
|
577
584
|
const indexQuery = `
|
|
@@ -624,6 +631,7 @@ var PgVector = class extends MastraVector {
|
|
|
624
631
|
const client = await this.pool.connect();
|
|
625
632
|
try {
|
|
626
633
|
await client.query(`DROP TABLE IF EXISTS ${indexName} CASCADE`);
|
|
634
|
+
this.createdIndexes.delete(indexName);
|
|
627
635
|
} catch (error) {
|
|
628
636
|
await client.query("ROLLBACK");
|
|
629
637
|
throw new Error(`Failed to delete vector table: ${error.message}`);
|
|
@@ -706,15 +714,16 @@ var PostgresStore = class extends MastraStorage {
|
|
|
706
714
|
}
|
|
707
715
|
);
|
|
708
716
|
}
|
|
709
|
-
getEvalsByAgentName(agentName, type) {
|
|
717
|
+
async getEvalsByAgentName(agentName, type) {
|
|
710
718
|
try {
|
|
711
719
|
const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = $1`;
|
|
712
720
|
const typeCondition = type === "test" ? " AND test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL" : type === "live" ? " AND (test_info IS NULL OR test_info->>'testPath' IS NULL)" : "";
|
|
713
721
|
const query = `${baseQuery}${typeCondition} ORDER BY created_at DESC`;
|
|
714
|
-
|
|
722
|
+
const rows = await this.db.manyOrNone(query, [agentName]);
|
|
723
|
+
return rows?.map((row) => this.transformEvalRow(row)) ?? [];
|
|
715
724
|
} catch (error) {
|
|
716
725
|
if (error instanceof Error && error.message.includes("relation") && error.message.includes("does not exist")) {
|
|
717
|
-
return
|
|
726
|
+
return [];
|
|
718
727
|
}
|
|
719
728
|
console.error("Failed to get evals for the specified agent: " + error?.message);
|
|
720
729
|
throw error;
|
|
@@ -801,11 +810,6 @@ var PostgresStore = class extends MastraStorage {
|
|
|
801
810
|
args.push(value);
|
|
802
811
|
}
|
|
803
812
|
}
|
|
804
|
-
console.log(
|
|
805
|
-
"QUERY",
|
|
806
|
-
`SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
|
|
807
|
-
args
|
|
808
|
-
);
|
|
809
813
|
const result = await this.db.manyOrNone(`SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`, args);
|
|
810
814
|
if (!result) {
|
|
811
815
|
return [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/pg",
|
|
3
|
-
"version": "0.2.7-alpha.
|
|
3
|
+
"version": "0.2.7-alpha.6",
|
|
4
4
|
"description": "Postgres provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
"./package.json": "./package.json"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"async-mutex": "^0.5.0",
|
|
22
23
|
"pg": "^8.13.3",
|
|
23
24
|
"pg-promise": "^11.11.0",
|
|
24
|
-
"
|
|
25
|
+
"xxhash-wasm": "^1.1.0",
|
|
26
|
+
"@mastra/core": "^0.8.0-alpha.6"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
27
29
|
"@microsoft/api-extractor": "^7.52.1",
|
package/src/storage/index.ts
CHANGED
|
@@ -47,7 +47,7 @@ export class PostgresStore extends MastraStorage {
|
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
|
|
50
|
+
async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
|
|
51
51
|
try {
|
|
52
52
|
const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = $1`;
|
|
53
53
|
const typeCondition =
|
|
@@ -59,11 +59,12 @@ export class PostgresStore extends MastraStorage {
|
|
|
59
59
|
|
|
60
60
|
const query = `${baseQuery}${typeCondition} ORDER BY created_at DESC`;
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
const rows = await this.db.manyOrNone(query, [agentName]);
|
|
63
|
+
return rows?.map(row => this.transformEvalRow(row)) ?? [];
|
|
63
64
|
} catch (error) {
|
|
64
65
|
// Handle case where table doesn't exist yet
|
|
65
66
|
if (error instanceof Error && error.message.includes('relation') && error.message.includes('does not exist')) {
|
|
66
|
-
return
|
|
67
|
+
return [];
|
|
67
68
|
}
|
|
68
69
|
console.error('Failed to get evals for the specified agent: ' + (error as any)?.message);
|
|
69
70
|
throw error;
|
|
@@ -170,12 +171,6 @@ export class PostgresStore extends MastraStorage {
|
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
console.log(
|
|
174
|
-
'QUERY',
|
|
175
|
-
`SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
|
|
176
|
-
args,
|
|
177
|
-
);
|
|
178
|
-
|
|
179
174
|
const result = await this.db.manyOrNone<{
|
|
180
175
|
id: string;
|
|
181
176
|
parentSpanId: string;
|
package/src/vector/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
2
1
|
import { MastraVector } from '@mastra/core/vector';
|
|
3
2
|
import type {
|
|
4
3
|
IndexStats,
|
|
@@ -11,7 +10,9 @@ import type {
|
|
|
11
10
|
CreateIndexArgs,
|
|
12
11
|
} from '@mastra/core/vector';
|
|
13
12
|
import type { VectorFilter } from '@mastra/core/vector/filter';
|
|
13
|
+
import { Mutex } from 'async-mutex';
|
|
14
14
|
import pg from 'pg';
|
|
15
|
+
import xxhash from 'xxhash-wasm';
|
|
15
16
|
|
|
16
17
|
import { PGFilterTranslator } from './filter';
|
|
17
18
|
import { buildFilterQuery } from './sql-builder';
|
|
@@ -60,7 +61,9 @@ type PgDefineIndexArgs = [string, 'cosine' | 'euclidean' | 'dotproduct', IndexCo
|
|
|
60
61
|
|
|
61
62
|
export class PgVector extends MastraVector {
|
|
62
63
|
private pool: pg.Pool;
|
|
63
|
-
private
|
|
64
|
+
private describeIndexCache: Map<string, PGIndexStats> = new Map();
|
|
65
|
+
private createdIndexes = new Map<string, number>();
|
|
66
|
+
private mutexesByName = new Map<string, Mutex>();
|
|
64
67
|
|
|
65
68
|
constructor(connectionString: string) {
|
|
66
69
|
super();
|
|
@@ -81,6 +84,26 @@ export class PgVector extends MastraVector {
|
|
|
81
84
|
'vector.type': 'postgres',
|
|
82
85
|
},
|
|
83
86
|
}) ?? basePool;
|
|
87
|
+
|
|
88
|
+
void (async () => {
|
|
89
|
+
// warm the created indexes cache so we don't need to check if indexes exist every time
|
|
90
|
+
const existingIndexes = await this.listIndexes();
|
|
91
|
+
void existingIndexes.map(async indexName => {
|
|
92
|
+
const info = await this.getIndexInfo(indexName);
|
|
93
|
+
const key = await this.getIndexCacheKey({
|
|
94
|
+
indexName,
|
|
95
|
+
metric: info.metric,
|
|
96
|
+
dimension: info.dimension,
|
|
97
|
+
type: info.type,
|
|
98
|
+
});
|
|
99
|
+
this.createdIndexes.set(indexName, key);
|
|
100
|
+
});
|
|
101
|
+
})();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private getMutexByName(indexName: string) {
|
|
105
|
+
if (!this.mutexesByName.has(indexName)) this.mutexesByName.set(indexName, new Mutex());
|
|
106
|
+
return this.mutexesByName.get(indexName)!;
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
transformFilter(filter?: VectorFilter) {
|
|
@@ -89,10 +112,10 @@ export class PgVector extends MastraVector {
|
|
|
89
112
|
}
|
|
90
113
|
|
|
91
114
|
async getIndexInfo(indexName: string): Promise<PGIndexStats> {
|
|
92
|
-
if (!this.
|
|
93
|
-
this.
|
|
115
|
+
if (!this.describeIndexCache.has(indexName)) {
|
|
116
|
+
this.describeIndexCache.set(indexName, await this.describeIndex(indexName));
|
|
94
117
|
}
|
|
95
|
-
return this.
|
|
118
|
+
return this.describeIndexCache.get(indexName)!;
|
|
96
119
|
}
|
|
97
120
|
|
|
98
121
|
async query(...args: ParamsToArgs<PgQueryVectorParams> | PgQueryVectorArgs): Promise<QueryResult[]> {
|
|
@@ -197,6 +220,15 @@ export class PgVector extends MastraVector {
|
|
|
197
220
|
}
|
|
198
221
|
}
|
|
199
222
|
|
|
223
|
+
private hasher = xxhash();
|
|
224
|
+
private async getIndexCacheKey(params: CreateIndexParams & { type: IndexType | undefined }) {
|
|
225
|
+
const input = params.indexName + params.dimension + params.metric + (params.type || 'ivfflat'); // ivfflat is default
|
|
226
|
+
return (await this.hasher).h32(input);
|
|
227
|
+
}
|
|
228
|
+
private cachedIndexExists(indexName: string, newKey: number) {
|
|
229
|
+
const existingIndexCacheKey = this.createdIndexes.get(indexName);
|
|
230
|
+
return existingIndexCacheKey && existingIndexCacheKey === newKey;
|
|
231
|
+
}
|
|
200
232
|
async createIndex(...args: ParamsToArgs<PgCreateIndexParams> | PgCreateIndexArgs): Promise<void> {
|
|
201
233
|
const params = this.normalizeArgs<PgCreateIndexParams, PgCreateIndexArgs>('createIndex', args, [
|
|
202
234
|
'indexConfig',
|
|
@@ -205,81 +237,74 @@ export class PgVector extends MastraVector {
|
|
|
205
237
|
|
|
206
238
|
const { indexName, dimension, metric = 'cosine', indexConfig = {}, buildIndex = true } = params;
|
|
207
239
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
240
|
+
// Validate inputs
|
|
241
|
+
if (!indexName.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
|
|
242
|
+
throw new Error('Invalid index name format');
|
|
243
|
+
}
|
|
244
|
+
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
245
|
+
throw new Error('Dimension must be a positive integer');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const indexCacheKey = await this.getIndexCacheKey({ indexName, dimension, type: indexConfig.type, metric });
|
|
249
|
+
if (this.cachedIndexExists(indexName, indexCacheKey)) {
|
|
250
|
+
// we already saw this index get created since the process started, no need to recreate it
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const mutex = this.getMutexByName(`create-${indexName}`);
|
|
255
|
+
// Use async-mutex instead of advisory lock for perf (over 2x as fast)
|
|
256
|
+
await mutex.runExclusive(async () => {
|
|
257
|
+
if (this.cachedIndexExists(indexName, indexCacheKey)) {
|
|
258
|
+
// this may have been created while we were waiting to acquire a lock
|
|
259
|
+
return;
|
|
216
260
|
}
|
|
217
261
|
|
|
218
|
-
|
|
219
|
-
|
|
262
|
+
const client = await this.pool.connect();
|
|
263
|
+
try {
|
|
264
|
+
// First check if vector extension is available
|
|
265
|
+
const extensionCheck = await client.query(`
|
|
220
266
|
SELECT EXISTS (
|
|
221
267
|
SELECT 1 FROM pg_available_extensions WHERE name = 'vector'
|
|
222
268
|
);
|
|
223
269
|
`);
|
|
224
270
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Get advisory lock using hash of index name
|
|
230
|
-
const hash = createHash('sha256').update(indexName).digest('hex');
|
|
231
|
-
const lockId = BigInt('0x' + hash.slice(0, 8)) % BigInt(2 ** 31); // Take first 8 chars and convert to number
|
|
232
|
-
const acquired = await client.query('SELECT pg_try_advisory_lock($1)', [lockId]);
|
|
233
|
-
|
|
234
|
-
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
235
|
-
// Check if table already exists
|
|
236
|
-
const exists = await client.query(
|
|
237
|
-
`
|
|
238
|
-
SELECT 1 FROM pg_class c
|
|
239
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
240
|
-
WHERE c.relname = $1
|
|
241
|
-
AND n.nspname = 'public'
|
|
242
|
-
`,
|
|
243
|
-
[indexName],
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
if (exists.rows.length > 0) {
|
|
247
|
-
// Table exists so return early
|
|
248
|
-
console.log(`Table ${indexName} already exists, skipping creation`);
|
|
249
|
-
return;
|
|
271
|
+
if (!extensionCheck.rows[0].exists) {
|
|
272
|
+
this.createdIndexes.delete(indexName);
|
|
273
|
+
throw new Error('PostgreSQL vector extension is not available. Please install it first.');
|
|
250
274
|
}
|
|
251
275
|
|
|
252
|
-
// Table doesn't exist, wait for lock
|
|
253
|
-
await client.query('SELECT pg_advisory_lock($1)', [lockId]);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
276
|
// Try to create extension
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
277
|
+
try {
|
|
278
|
+
await client.query(`
|
|
279
|
+
DO $$
|
|
280
|
+
BEGIN
|
|
281
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
282
|
+
|
|
283
|
+
CREATE TABLE IF NOT EXISTS ${indexName} (
|
|
284
|
+
id SERIAL PRIMARY KEY,
|
|
285
|
+
vector_id TEXT UNIQUE NOT NULL,
|
|
286
|
+
embedding vector(${dimension}),
|
|
287
|
+
metadata JSONB DEFAULT '{}'::jsonb
|
|
288
|
+
);
|
|
289
|
+
END $$;
|
|
290
|
+
`);
|
|
291
|
+
this.createdIndexes.set(indexName, indexCacheKey);
|
|
292
|
+
} catch (e) {
|
|
293
|
+
this.createdIndexes.delete(indexName);
|
|
294
|
+
throw e;
|
|
295
|
+
}
|
|
273
296
|
|
|
274
|
-
|
|
275
|
-
|
|
297
|
+
if (buildIndex) {
|
|
298
|
+
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
299
|
+
}
|
|
300
|
+
} catch (error: any) {
|
|
301
|
+
this.createdIndexes.delete(indexName);
|
|
302
|
+
console.error('Failed to create vector table:', error);
|
|
303
|
+
throw error;
|
|
304
|
+
} finally {
|
|
305
|
+
client.release();
|
|
276
306
|
}
|
|
277
|
-
}
|
|
278
|
-
console.error('Failed to create vector table:', error);
|
|
279
|
-
throw error;
|
|
280
|
-
} finally {
|
|
281
|
-
client.release();
|
|
282
|
-
}
|
|
307
|
+
});
|
|
283
308
|
}
|
|
284
309
|
|
|
285
310
|
/**
|
|
@@ -310,40 +335,15 @@ export class PgVector extends MastraVector {
|
|
|
310
335
|
}
|
|
311
336
|
|
|
312
337
|
private async setupIndex({ indexName, metric, indexConfig }: PgDefineIndexParams, client: pg.PoolClient) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
.
|
|
317
|
-
|
|
318
|
-
const acquired = await client.query('SELECT pg_try_advisory_lock($1)', [lockId]);
|
|
319
|
-
|
|
320
|
-
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
321
|
-
// Check if index already exists
|
|
322
|
-
const exists = await client.query(
|
|
323
|
-
`
|
|
324
|
-
SELECT 1 FROM pg_class c
|
|
325
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
326
|
-
WHERE c.relname = $1
|
|
327
|
-
AND n.nspname = 'public'
|
|
328
|
-
`,
|
|
329
|
-
[`${indexName}_vector_idx`],
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
if (exists.rows.length > 0) {
|
|
333
|
-
console.log(`Index ${indexName}_vector_idx already exists, skipping creation`);
|
|
334
|
-
this.indexCache.delete(indexName); // Still clear cache since we checked
|
|
335
|
-
return;
|
|
338
|
+
const mutex = this.getMutexByName(`build-${indexName}`);
|
|
339
|
+
// Use async-mutex instead of advisory lock for perf (over 2x as fast)
|
|
340
|
+
await mutex.runExclusive(async () => {
|
|
341
|
+
if (this.createdIndexes.has(indexName)) {
|
|
342
|
+
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
336
343
|
}
|
|
337
344
|
|
|
338
|
-
// Index doesn't exist, wait for lock
|
|
339
|
-
await client.query('SELECT pg_advisory_lock($1)', [lockId]);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
try {
|
|
343
|
-
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
344
|
-
|
|
345
345
|
if (indexConfig.type === 'flat') {
|
|
346
|
-
this.
|
|
346
|
+
this.describeIndexCache.delete(indexName);
|
|
347
347
|
return;
|
|
348
348
|
}
|
|
349
349
|
|
|
@@ -381,10 +381,7 @@ export class PgVector extends MastraVector {
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
await client.query(indexSQL);
|
|
384
|
-
|
|
385
|
-
} finally {
|
|
386
|
-
await client.query('SELECT pg_advisory_unlock($1)', [lockId]);
|
|
387
|
-
}
|
|
384
|
+
});
|
|
388
385
|
}
|
|
389
386
|
|
|
390
387
|
async listIndexes(): Promise<string[]> {
|
|
@@ -416,8 +413,7 @@ export class PgVector extends MastraVector {
|
|
|
416
413
|
`;
|
|
417
414
|
|
|
418
415
|
// Get row count
|
|
419
|
-
const countQuery = `
|
|
420
|
-
SELECT COUNT(*) as count
|
|
416
|
+
const countQuery = ` SELECT COUNT(*) as count
|
|
421
417
|
FROM ${indexName};
|
|
422
418
|
`;
|
|
423
419
|
|
|
@@ -486,6 +482,7 @@ export class PgVector extends MastraVector {
|
|
|
486
482
|
try {
|
|
487
483
|
// Drop the table
|
|
488
484
|
await client.query(`DROP TABLE IF EXISTS ${indexName} CASCADE`);
|
|
485
|
+
this.createdIndexes.delete(indexName);
|
|
489
486
|
} catch (error: any) {
|
|
490
487
|
await client.query('ROLLBACK');
|
|
491
488
|
throw new Error(`Failed to delete vector table: ${error.message}`);
|