@mastra/pg 0.2.6-alpha.2 → 0.2.6-alpha.4
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 +15 -0
- package/dist/_tsup-dts-rollup.d.cts +1 -0
- package/dist/_tsup-dts-rollup.d.ts +1 -0
- package/dist/index.cjs +61 -7
- package/dist/index.js +61 -7
- package/package.json +2 -2
- package/src/vector/index.test.ts +44 -1
- package/src/vector/index.ts +82 -9
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/pg@0.2.6-alpha.
|
|
2
|
+
> @mastra/pg@0.2.6-alpha.4 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 10085ms
|
|
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 10753ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
21
|
-
[32mESM[39m ⚡️ Build success in
|
|
22
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
23
|
-
[32mCJS[39m ⚡️ Build success in
|
|
20
|
+
[32mESM[39m [1mdist/index.js [22m[32m36.43 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 1230ms
|
|
22
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m36.85 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 1230ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @mastra/pg
|
|
2
2
|
|
|
3
|
+
## 0.2.6-alpha.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e91bee7: Added helper method for both createindex and buildIndex
|
|
8
|
+
|
|
9
|
+
## 0.2.6-alpha.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 7172059: Update PG Vector to use handle concurrent createIndex
|
|
14
|
+
- Updated dependencies [b3b34f5]
|
|
15
|
+
- Updated dependencies [a4686e8]
|
|
16
|
+
- @mastra/core@0.7.0-alpha.3
|
|
17
|
+
|
|
3
18
|
## 0.2.6-alpha.2
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -211,6 +211,7 @@ declare class PgVector extends MastraVector {
|
|
|
211
211
|
*/
|
|
212
212
|
defineIndex(indexName: string, metric: "cosine" | "euclidean" | "dotproduct" | undefined, indexConfig: IndexConfig): Promise<void>;
|
|
213
213
|
buildIndex(...args: ParamsToArgs<PgDefineIndexParams> | PgDefineIndexArgs): Promise<void>;
|
|
214
|
+
private setupIndex;
|
|
214
215
|
listIndexes(): Promise<string[]>;
|
|
215
216
|
describeIndex(indexName: string): Promise<PGIndexStats>;
|
|
216
217
|
deleteIndex(indexName: string): Promise<void>;
|
|
@@ -211,6 +211,7 @@ declare class PgVector extends MastraVector {
|
|
|
211
211
|
*/
|
|
212
212
|
defineIndex(indexName: string, metric: "cosine" | "euclidean" | "dotproduct" | undefined, indexConfig: IndexConfig): Promise<void>;
|
|
213
213
|
buildIndex(...args: ParamsToArgs<PgDefineIndexParams> | PgDefineIndexArgs): Promise<void>;
|
|
214
|
+
private setupIndex;
|
|
214
215
|
listIndexes(): Promise<string[]>;
|
|
215
216
|
describeIndex(indexName: string): Promise<PGIndexStats>;
|
|
216
217
|
deleteIndex(indexName: string): Promise<void>;
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var crypto$1 = require('crypto');
|
|
3
4
|
var vector = require('@mastra/core/vector');
|
|
4
5
|
var pg = require('pg');
|
|
5
6
|
var filter = require('@mastra/core/vector/filter');
|
|
@@ -420,8 +421,28 @@ var PgVector = class extends vector.MastraVector {
|
|
|
420
421
|
if (!extensionCheck.rows[0].exists) {
|
|
421
422
|
throw new Error("PostgreSQL vector extension is not available. Please install it first.");
|
|
422
423
|
}
|
|
423
|
-
|
|
424
|
-
|
|
424
|
+
const hash = crypto$1.createHash("sha256").update(indexName).digest("hex");
|
|
425
|
+
const lockId = BigInt("0x" + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
426
|
+
const acquired = await client.query("SELECT pg_try_advisory_lock($1)", [lockId]);
|
|
427
|
+
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
428
|
+
const exists = await client.query(
|
|
429
|
+
`
|
|
430
|
+
SELECT 1 FROM pg_class c
|
|
431
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
432
|
+
WHERE c.relname = $1
|
|
433
|
+
AND n.nspname = 'public'
|
|
434
|
+
`,
|
|
435
|
+
[indexName]
|
|
436
|
+
);
|
|
437
|
+
if (exists.rows.length > 0) {
|
|
438
|
+
console.log(`Table ${indexName} already exists, skipping creation`);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
await client.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
445
|
+
await client.query(`
|
|
425
446
|
CREATE TABLE IF NOT EXISTS ${indexName} (
|
|
426
447
|
id SERIAL PRIMARY KEY,
|
|
427
448
|
vector_id TEXT UNIQUE NOT NULL,
|
|
@@ -429,8 +450,11 @@ var PgVector = class extends vector.MastraVector {
|
|
|
429
450
|
metadata JSONB DEFAULT '{}'::jsonb
|
|
430
451
|
);
|
|
431
452
|
`);
|
|
453
|
+
} finally {
|
|
454
|
+
await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
455
|
+
}
|
|
432
456
|
if (buildIndex) {
|
|
433
|
-
await this.
|
|
457
|
+
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
434
458
|
}
|
|
435
459
|
} catch (error) {
|
|
436
460
|
console.error("Failed to create vector table:", error);
|
|
@@ -452,16 +476,46 @@ var PgVector = class extends vector.MastraVector {
|
|
|
452
476
|
]);
|
|
453
477
|
const { indexName, metric = "cosine", indexConfig } = params;
|
|
454
478
|
const client = await this.pool.connect();
|
|
479
|
+
try {
|
|
480
|
+
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
481
|
+
} finally {
|
|
482
|
+
client.release();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async setupIndex({ indexName, metric, indexConfig }, client) {
|
|
486
|
+
const hash = crypto$1.createHash("sha256").update("build:" + indexName).digest("hex");
|
|
487
|
+
const lockId = BigInt("0x" + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
488
|
+
const acquired = await client.query("SELECT pg_try_advisory_lock($1)", [lockId]);
|
|
489
|
+
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
490
|
+
const exists = await client.query(
|
|
491
|
+
`
|
|
492
|
+
SELECT 1 FROM pg_class c
|
|
493
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
494
|
+
WHERE c.relname = $1
|
|
495
|
+
AND n.nspname = 'public'
|
|
496
|
+
`,
|
|
497
|
+
[`${indexName}_vector_idx`]
|
|
498
|
+
);
|
|
499
|
+
if (exists.rows.length > 0) {
|
|
500
|
+
console.log(`Index ${indexName}_vector_idx already exists, skipping creation`);
|
|
501
|
+
this.indexCache.delete(indexName);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
await client.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
505
|
+
}
|
|
455
506
|
try {
|
|
456
507
|
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
457
|
-
if (indexConfig.type === "flat")
|
|
508
|
+
if (indexConfig.type === "flat") {
|
|
509
|
+
this.indexCache.delete(indexName);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
458
512
|
const metricOp = metric === "cosine" ? "vector_cosine_ops" : metric === "euclidean" ? "vector_l2_ops" : "vector_ip_ops";
|
|
459
513
|
let indexSQL;
|
|
460
514
|
if (indexConfig.type === "hnsw") {
|
|
461
515
|
const m = indexConfig.hnsw?.m ?? 8;
|
|
462
516
|
const efConstruction = indexConfig.hnsw?.efConstruction ?? 32;
|
|
463
517
|
indexSQL = `
|
|
464
|
-
CREATE INDEX ${indexName}_vector_idx
|
|
518
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
465
519
|
ON ${indexName}
|
|
466
520
|
USING hnsw (embedding ${metricOp})
|
|
467
521
|
WITH (
|
|
@@ -478,7 +532,7 @@ var PgVector = class extends vector.MastraVector {
|
|
|
478
532
|
lists = Math.max(100, Math.min(4e3, Math.floor(Math.sqrt(size) * 2)));
|
|
479
533
|
}
|
|
480
534
|
indexSQL = `
|
|
481
|
-
CREATE INDEX ${indexName}_vector_idx
|
|
535
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
482
536
|
ON ${indexName}
|
|
483
537
|
USING ivfflat (embedding ${metricOp})
|
|
484
538
|
WITH (lists = ${lists});
|
|
@@ -487,7 +541,7 @@ var PgVector = class extends vector.MastraVector {
|
|
|
487
541
|
await client.query(indexSQL);
|
|
488
542
|
this.indexCache.delete(indexName);
|
|
489
543
|
} finally {
|
|
490
|
-
client.
|
|
544
|
+
await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
491
545
|
}
|
|
492
546
|
}
|
|
493
547
|
async listIndexes() {
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
1
2
|
import { MastraVector } from '@mastra/core/vector';
|
|
2
3
|
import pg from 'pg';
|
|
3
4
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
@@ -413,8 +414,28 @@ var PgVector = class extends MastraVector {
|
|
|
413
414
|
if (!extensionCheck.rows[0].exists) {
|
|
414
415
|
throw new Error("PostgreSQL vector extension is not available. Please install it first.");
|
|
415
416
|
}
|
|
416
|
-
|
|
417
|
-
|
|
417
|
+
const hash = createHash("sha256").update(indexName).digest("hex");
|
|
418
|
+
const lockId = BigInt("0x" + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
419
|
+
const acquired = await client.query("SELECT pg_try_advisory_lock($1)", [lockId]);
|
|
420
|
+
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
421
|
+
const exists = await client.query(
|
|
422
|
+
`
|
|
423
|
+
SELECT 1 FROM pg_class c
|
|
424
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
425
|
+
WHERE c.relname = $1
|
|
426
|
+
AND n.nspname = 'public'
|
|
427
|
+
`,
|
|
428
|
+
[indexName]
|
|
429
|
+
);
|
|
430
|
+
if (exists.rows.length > 0) {
|
|
431
|
+
console.log(`Table ${indexName} already exists, skipping creation`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
await client.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
438
|
+
await client.query(`
|
|
418
439
|
CREATE TABLE IF NOT EXISTS ${indexName} (
|
|
419
440
|
id SERIAL PRIMARY KEY,
|
|
420
441
|
vector_id TEXT UNIQUE NOT NULL,
|
|
@@ -422,8 +443,11 @@ var PgVector = class extends MastraVector {
|
|
|
422
443
|
metadata JSONB DEFAULT '{}'::jsonb
|
|
423
444
|
);
|
|
424
445
|
`);
|
|
446
|
+
} finally {
|
|
447
|
+
await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
448
|
+
}
|
|
425
449
|
if (buildIndex) {
|
|
426
|
-
await this.
|
|
450
|
+
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
427
451
|
}
|
|
428
452
|
} catch (error) {
|
|
429
453
|
console.error("Failed to create vector table:", error);
|
|
@@ -445,16 +469,46 @@ var PgVector = class extends MastraVector {
|
|
|
445
469
|
]);
|
|
446
470
|
const { indexName, metric = "cosine", indexConfig } = params;
|
|
447
471
|
const client = await this.pool.connect();
|
|
472
|
+
try {
|
|
473
|
+
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
474
|
+
} finally {
|
|
475
|
+
client.release();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async setupIndex({ indexName, metric, indexConfig }, client) {
|
|
479
|
+
const hash = createHash("sha256").update("build:" + indexName).digest("hex");
|
|
480
|
+
const lockId = BigInt("0x" + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
481
|
+
const acquired = await client.query("SELECT pg_try_advisory_lock($1)", [lockId]);
|
|
482
|
+
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
483
|
+
const exists = await client.query(
|
|
484
|
+
`
|
|
485
|
+
SELECT 1 FROM pg_class c
|
|
486
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
487
|
+
WHERE c.relname = $1
|
|
488
|
+
AND n.nspname = 'public'
|
|
489
|
+
`,
|
|
490
|
+
[`${indexName}_vector_idx`]
|
|
491
|
+
);
|
|
492
|
+
if (exists.rows.length > 0) {
|
|
493
|
+
console.log(`Index ${indexName}_vector_idx already exists, skipping creation`);
|
|
494
|
+
this.indexCache.delete(indexName);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
await client.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
498
|
+
}
|
|
448
499
|
try {
|
|
449
500
|
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
450
|
-
if (indexConfig.type === "flat")
|
|
501
|
+
if (indexConfig.type === "flat") {
|
|
502
|
+
this.indexCache.delete(indexName);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
451
505
|
const metricOp = metric === "cosine" ? "vector_cosine_ops" : metric === "euclidean" ? "vector_l2_ops" : "vector_ip_ops";
|
|
452
506
|
let indexSQL;
|
|
453
507
|
if (indexConfig.type === "hnsw") {
|
|
454
508
|
const m = indexConfig.hnsw?.m ?? 8;
|
|
455
509
|
const efConstruction = indexConfig.hnsw?.efConstruction ?? 32;
|
|
456
510
|
indexSQL = `
|
|
457
|
-
CREATE INDEX ${indexName}_vector_idx
|
|
511
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
458
512
|
ON ${indexName}
|
|
459
513
|
USING hnsw (embedding ${metricOp})
|
|
460
514
|
WITH (
|
|
@@ -471,7 +525,7 @@ var PgVector = class extends MastraVector {
|
|
|
471
525
|
lists = Math.max(100, Math.min(4e3, Math.floor(Math.sqrt(size) * 2)));
|
|
472
526
|
}
|
|
473
527
|
indexSQL = `
|
|
474
|
-
CREATE INDEX ${indexName}_vector_idx
|
|
528
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
475
529
|
ON ${indexName}
|
|
476
530
|
USING ivfflat (embedding ${metricOp})
|
|
477
531
|
WITH (lists = ${lists});
|
|
@@ -480,7 +534,7 @@ var PgVector = class extends MastraVector {
|
|
|
480
534
|
await client.query(indexSQL);
|
|
481
535
|
this.indexCache.delete(indexName);
|
|
482
536
|
} finally {
|
|
483
|
-
client.
|
|
537
|
+
await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
484
538
|
}
|
|
485
539
|
}
|
|
486
540
|
async listIndexes() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/pg",
|
|
3
|
-
"version": "0.2.6-alpha.
|
|
3
|
+
"version": "0.2.6-alpha.4",
|
|
4
4
|
"description": "Postgres provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"pg": "^8.13.3",
|
|
23
23
|
"pg-promise": "^11.11.0",
|
|
24
|
-
"@mastra/core": "^0.7.0-alpha.
|
|
24
|
+
"@mastra/core": "^0.7.0-alpha.3"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@microsoft/api-extractor": "^7.52.1",
|
package/src/vector/index.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { QueryResult } from '@mastra/core';
|
|
1
2
|
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest';
|
|
2
3
|
|
|
3
4
|
import { PgVector } from '.';
|
|
4
|
-
import { type QueryResult } from '@mastra/core';
|
|
5
5
|
|
|
6
6
|
describe('PgVector', () => {
|
|
7
7
|
let vectorDB: PgVector;
|
|
@@ -1812,4 +1812,47 @@ describe('PgVector', () => {
|
|
|
1812
1812
|
await expect(vectorDB.buildIndex(indexName, 'cosine', { type: 'flat' })).resolves.not.toThrow();
|
|
1813
1813
|
});
|
|
1814
1814
|
});
|
|
1815
|
+
|
|
1816
|
+
describe('Concurrent Operations', () => {
|
|
1817
|
+
it('should handle concurrent index creation attempts', async () => {
|
|
1818
|
+
const indexName = 'concurrent_test_index';
|
|
1819
|
+
const dimension = 384;
|
|
1820
|
+
|
|
1821
|
+
// Create multiple promises trying to create the same index
|
|
1822
|
+
const promises = Array(5)
|
|
1823
|
+
.fill(null)
|
|
1824
|
+
.map(() => vectorDB.createIndex({ indexName, dimension }));
|
|
1825
|
+
|
|
1826
|
+
// All should resolve without error - subsequent attempts should be no-ops
|
|
1827
|
+
await expect(Promise.all(promises)).resolves.not.toThrow();
|
|
1828
|
+
|
|
1829
|
+
// Verify only one index was actually created
|
|
1830
|
+
const stats = await vectorDB.describeIndex(indexName);
|
|
1831
|
+
expect(stats.dimension).toBe(dimension);
|
|
1832
|
+
|
|
1833
|
+
await vectorDB.deleteIndex(indexName);
|
|
1834
|
+
});
|
|
1835
|
+
|
|
1836
|
+
it('should handle concurrent buildIndex attempts', async () => {
|
|
1837
|
+
const indexName = 'concurrent_build_test';
|
|
1838
|
+
await vectorDB.createIndex({ indexName, dimension: 384 });
|
|
1839
|
+
|
|
1840
|
+
const promises = Array(5)
|
|
1841
|
+
.fill(null)
|
|
1842
|
+
.map(() =>
|
|
1843
|
+
vectorDB.buildIndex({
|
|
1844
|
+
indexName,
|
|
1845
|
+
metric: 'cosine',
|
|
1846
|
+
indexConfig: { type: 'ivfflat', ivf: { lists: 100 } },
|
|
1847
|
+
}),
|
|
1848
|
+
);
|
|
1849
|
+
|
|
1850
|
+
await expect(Promise.all(promises)).resolves.not.toThrow();
|
|
1851
|
+
|
|
1852
|
+
const stats = await vectorDB.describeIndex(indexName);
|
|
1853
|
+
expect(stats.type).toBe('ivfflat');
|
|
1854
|
+
|
|
1855
|
+
await vectorDB.deleteIndex(indexName);
|
|
1856
|
+
});
|
|
1857
|
+
});
|
|
1815
1858
|
});
|
package/src/vector/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
1
2
|
import { MastraVector } from '@mastra/core/vector';
|
|
2
3
|
import type {
|
|
3
4
|
IndexStats,
|
|
@@ -215,11 +216,39 @@ export class PgVector extends MastraVector {
|
|
|
215
216
|
throw new Error('PostgreSQL vector extension is not available. Please install it first.');
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
//
|
|
219
|
-
|
|
219
|
+
// Get advisory lock using hash of index name
|
|
220
|
+
const hash = createHash('sha256').update(indexName).digest('hex');
|
|
221
|
+
const lockId = BigInt('0x' + hash.slice(0, 8)) % BigInt(2 ** 31); // Take first 8 chars and convert to number
|
|
222
|
+
const acquired = await client.query('SELECT pg_try_advisory_lock($1)', [lockId]);
|
|
223
|
+
|
|
224
|
+
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
225
|
+
// Check if table already exists
|
|
226
|
+
const exists = await client.query(
|
|
227
|
+
`
|
|
228
|
+
SELECT 1 FROM pg_class c
|
|
229
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
230
|
+
WHERE c.relname = $1
|
|
231
|
+
AND n.nspname = 'public'
|
|
232
|
+
`,
|
|
233
|
+
[indexName],
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (exists.rows.length > 0) {
|
|
237
|
+
// Table exists so return early
|
|
238
|
+
console.log(`Table ${indexName} already exists, skipping creation`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Table doesn't exist, wait for lock
|
|
243
|
+
await client.query('SELECT pg_advisory_lock($1)', [lockId]);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
// Try to create extension
|
|
248
|
+
await client.query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
220
249
|
|
|
221
|
-
|
|
222
|
-
|
|
250
|
+
// Create the table with explicit schema
|
|
251
|
+
await client.query(`
|
|
223
252
|
CREATE TABLE IF NOT EXISTS ${indexName} (
|
|
224
253
|
id SERIAL PRIMARY KEY,
|
|
225
254
|
vector_id TEXT UNIQUE NOT NULL,
|
|
@@ -227,9 +256,13 @@ export class PgVector extends MastraVector {
|
|
|
227
256
|
metadata JSONB DEFAULT '{}'::jsonb
|
|
228
257
|
);
|
|
229
258
|
`);
|
|
259
|
+
} finally {
|
|
260
|
+
// Always release lock
|
|
261
|
+
await client.query('SELECT pg_advisory_unlock($1)', [lockId]);
|
|
262
|
+
}
|
|
230
263
|
|
|
231
264
|
if (buildIndex) {
|
|
232
|
-
await this.
|
|
265
|
+
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
233
266
|
}
|
|
234
267
|
} catch (error: any) {
|
|
235
268
|
console.error('Failed to create vector table:', error);
|
|
@@ -259,10 +292,50 @@ export class PgVector extends MastraVector {
|
|
|
259
292
|
const { indexName, metric = 'cosine', indexConfig } = params;
|
|
260
293
|
|
|
261
294
|
const client = await this.pool.connect();
|
|
295
|
+
try {
|
|
296
|
+
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
297
|
+
} finally {
|
|
298
|
+
client.release();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private async setupIndex({ indexName, metric, indexConfig }: PgDefineIndexParams, client: pg.PoolClient) {
|
|
303
|
+
// Use a different hash prefix for buildIndex locks to avoid conflicts with createIndex
|
|
304
|
+
const hash = createHash('sha256')
|
|
305
|
+
.update('build:' + indexName)
|
|
306
|
+
.digest('hex');
|
|
307
|
+
const lockId = BigInt('0x' + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
308
|
+
const acquired = await client.query('SELECT pg_try_advisory_lock($1)', [lockId]);
|
|
309
|
+
|
|
310
|
+
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
311
|
+
// Check if index already exists
|
|
312
|
+
const exists = await client.query(
|
|
313
|
+
`
|
|
314
|
+
SELECT 1 FROM pg_class c
|
|
315
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
316
|
+
WHERE c.relname = $1
|
|
317
|
+
AND n.nspname = 'public'
|
|
318
|
+
`,
|
|
319
|
+
[`${indexName}_vector_idx`],
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (exists.rows.length > 0) {
|
|
323
|
+
console.log(`Index ${indexName}_vector_idx already exists, skipping creation`);
|
|
324
|
+
this.indexCache.delete(indexName); // Still clear cache since we checked
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Index doesn't exist, wait for lock
|
|
329
|
+
await client.query('SELECT pg_advisory_lock($1)', [lockId]);
|
|
330
|
+
}
|
|
331
|
+
|
|
262
332
|
try {
|
|
263
333
|
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
264
334
|
|
|
265
|
-
if (indexConfig.type === 'flat')
|
|
335
|
+
if (indexConfig.type === 'flat') {
|
|
336
|
+
this.indexCache.delete(indexName);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
266
339
|
|
|
267
340
|
const metricOp =
|
|
268
341
|
metric === 'cosine' ? 'vector_cosine_ops' : metric === 'euclidean' ? 'vector_l2_ops' : 'vector_ip_ops';
|
|
@@ -273,7 +346,7 @@ export class PgVector extends MastraVector {
|
|
|
273
346
|
const efConstruction = indexConfig.hnsw?.efConstruction ?? 32;
|
|
274
347
|
|
|
275
348
|
indexSQL = `
|
|
276
|
-
CREATE INDEX ${indexName}_vector_idx
|
|
349
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
277
350
|
ON ${indexName}
|
|
278
351
|
USING hnsw (embedding ${metricOp})
|
|
279
352
|
WITH (
|
|
@@ -290,7 +363,7 @@ export class PgVector extends MastraVector {
|
|
|
290
363
|
lists = Math.max(100, Math.min(4000, Math.floor(Math.sqrt(size) * 2)));
|
|
291
364
|
}
|
|
292
365
|
indexSQL = `
|
|
293
|
-
CREATE INDEX ${indexName}_vector_idx
|
|
366
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
294
367
|
ON ${indexName}
|
|
295
368
|
USING ivfflat (embedding ${metricOp})
|
|
296
369
|
WITH (lists = ${lists});
|
|
@@ -300,7 +373,7 @@ export class PgVector extends MastraVector {
|
|
|
300
373
|
await client.query(indexSQL);
|
|
301
374
|
this.indexCache.delete(indexName);
|
|
302
375
|
} finally {
|
|
303
|
-
client.
|
|
376
|
+
await client.query('SELECT pg_advisory_unlock($1)', [lockId]);
|
|
304
377
|
}
|
|
305
378
|
}
|
|
306
379
|
|