@mastra/pg 0.2.6-alpha.2 → 0.2.6-alpha.3
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 +9 -0
- package/dist/index.cjs +73 -22
- package/dist/index.js +73 -22
- package/package.json +2 -2
- package/src/vector/index.test.ts +44 -1
- package/src/vector/index.ts +95 -26
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.3 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 10734ms
|
|
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 11829ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
20
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m36.78 KB[39m
|
|
21
|
+
[32mCJS[39m ⚡️ Build success in 1386ms
|
|
22
|
+
[32mESM[39m [1mdist/index.js [22m[32m36.37 KB[39m
|
|
23
|
+
[32mESM[39m ⚡️ Build success in 1387ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @mastra/pg
|
|
2
2
|
|
|
3
|
+
## 0.2.6-alpha.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7172059: Update PG Vector to use handle concurrent createIndex
|
|
8
|
+
- Updated dependencies [b3b34f5]
|
|
9
|
+
- Updated dependencies [a4686e8]
|
|
10
|
+
- @mastra/core@0.7.0-alpha.3
|
|
11
|
+
|
|
3
12
|
## 0.2.6-alpha.2
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
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,6 +450,9 @@ 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
457
|
await this.buildIndex({ indexName, metric, indexConfig });
|
|
434
458
|
}
|
|
@@ -453,15 +477,39 @@ var PgVector = class extends vector.MastraVector {
|
|
|
453
477
|
const { indexName, metric = "cosine", indexConfig } = params;
|
|
454
478
|
const client = await this.pool.connect();
|
|
455
479
|
try {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
480
|
+
const hash = crypto$1.createHash("sha256").update("build:" + indexName).digest("hex");
|
|
481
|
+
const lockId = BigInt("0x" + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
482
|
+
const acquired = await client.query("SELECT pg_try_advisory_lock($1)", [lockId]);
|
|
483
|
+
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
484
|
+
const exists = await client.query(
|
|
485
|
+
`
|
|
486
|
+
SELECT 1 FROM pg_class c
|
|
487
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
488
|
+
WHERE c.relname = $1
|
|
489
|
+
AND n.nspname = 'public'
|
|
490
|
+
`,
|
|
491
|
+
[`${indexName}_vector_idx`]
|
|
492
|
+
);
|
|
493
|
+
if (exists.rows.length > 0) {
|
|
494
|
+
console.log(`Index ${indexName}_vector_idx already exists, skipping creation`);
|
|
495
|
+
this.indexCache.delete(indexName);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
await client.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
499
|
+
}
|
|
500
|
+
try {
|
|
501
|
+
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
502
|
+
if (indexConfig.type === "flat") {
|
|
503
|
+
this.indexCache.delete(indexName);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const metricOp = metric === "cosine" ? "vector_cosine_ops" : metric === "euclidean" ? "vector_l2_ops" : "vector_ip_ops";
|
|
507
|
+
let indexSQL;
|
|
508
|
+
if (indexConfig.type === "hnsw") {
|
|
509
|
+
const m = indexConfig.hnsw?.m ?? 8;
|
|
510
|
+
const efConstruction = indexConfig.hnsw?.efConstruction ?? 32;
|
|
511
|
+
indexSQL = `
|
|
512
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
465
513
|
ON ${indexName}
|
|
466
514
|
USING hnsw (embedding ${metricOp})
|
|
467
515
|
WITH (
|
|
@@ -469,23 +517,26 @@ var PgVector = class extends vector.MastraVector {
|
|
|
469
517
|
ef_construction = ${efConstruction}
|
|
470
518
|
)
|
|
471
519
|
`;
|
|
472
|
-
} else {
|
|
473
|
-
let lists;
|
|
474
|
-
if (indexConfig.ivf?.lists) {
|
|
475
|
-
lists = indexConfig.ivf.lists;
|
|
476
520
|
} else {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
521
|
+
let lists;
|
|
522
|
+
if (indexConfig.ivf?.lists) {
|
|
523
|
+
lists = indexConfig.ivf.lists;
|
|
524
|
+
} else {
|
|
525
|
+
const size = (await client.query(`SELECT COUNT(*) FROM ${indexName}`)).rows[0].count;
|
|
526
|
+
lists = Math.max(100, Math.min(4e3, Math.floor(Math.sqrt(size) * 2)));
|
|
527
|
+
}
|
|
528
|
+
indexSQL = `
|
|
529
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
482
530
|
ON ${indexName}
|
|
483
531
|
USING ivfflat (embedding ${metricOp})
|
|
484
532
|
WITH (lists = ${lists});
|
|
485
533
|
`;
|
|
534
|
+
}
|
|
535
|
+
await client.query(indexSQL);
|
|
536
|
+
this.indexCache.delete(indexName);
|
|
537
|
+
} finally {
|
|
538
|
+
await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
486
539
|
}
|
|
487
|
-
await client.query(indexSQL);
|
|
488
|
-
this.indexCache.delete(indexName);
|
|
489
540
|
} finally {
|
|
490
541
|
client.release();
|
|
491
542
|
}
|
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,6 +443,9 @@ 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
450
|
await this.buildIndex({ indexName, metric, indexConfig });
|
|
427
451
|
}
|
|
@@ -446,15 +470,39 @@ var PgVector = class extends MastraVector {
|
|
|
446
470
|
const { indexName, metric = "cosine", indexConfig } = params;
|
|
447
471
|
const client = await this.pool.connect();
|
|
448
472
|
try {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
473
|
+
const hash = createHash("sha256").update("build:" + indexName).digest("hex");
|
|
474
|
+
const lockId = BigInt("0x" + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
475
|
+
const acquired = await client.query("SELECT pg_try_advisory_lock($1)", [lockId]);
|
|
476
|
+
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
477
|
+
const exists = await client.query(
|
|
478
|
+
`
|
|
479
|
+
SELECT 1 FROM pg_class c
|
|
480
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
481
|
+
WHERE c.relname = $1
|
|
482
|
+
AND n.nspname = 'public'
|
|
483
|
+
`,
|
|
484
|
+
[`${indexName}_vector_idx`]
|
|
485
|
+
);
|
|
486
|
+
if (exists.rows.length > 0) {
|
|
487
|
+
console.log(`Index ${indexName}_vector_idx already exists, skipping creation`);
|
|
488
|
+
this.indexCache.delete(indexName);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
await client.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
495
|
+
if (indexConfig.type === "flat") {
|
|
496
|
+
this.indexCache.delete(indexName);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const metricOp = metric === "cosine" ? "vector_cosine_ops" : metric === "euclidean" ? "vector_l2_ops" : "vector_ip_ops";
|
|
500
|
+
let indexSQL;
|
|
501
|
+
if (indexConfig.type === "hnsw") {
|
|
502
|
+
const m = indexConfig.hnsw?.m ?? 8;
|
|
503
|
+
const efConstruction = indexConfig.hnsw?.efConstruction ?? 32;
|
|
504
|
+
indexSQL = `
|
|
505
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
458
506
|
ON ${indexName}
|
|
459
507
|
USING hnsw (embedding ${metricOp})
|
|
460
508
|
WITH (
|
|
@@ -462,23 +510,26 @@ var PgVector = class extends MastraVector {
|
|
|
462
510
|
ef_construction = ${efConstruction}
|
|
463
511
|
)
|
|
464
512
|
`;
|
|
465
|
-
} else {
|
|
466
|
-
let lists;
|
|
467
|
-
if (indexConfig.ivf?.lists) {
|
|
468
|
-
lists = indexConfig.ivf.lists;
|
|
469
513
|
} else {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
514
|
+
let lists;
|
|
515
|
+
if (indexConfig.ivf?.lists) {
|
|
516
|
+
lists = indexConfig.ivf.lists;
|
|
517
|
+
} else {
|
|
518
|
+
const size = (await client.query(`SELECT COUNT(*) FROM ${indexName}`)).rows[0].count;
|
|
519
|
+
lists = Math.max(100, Math.min(4e3, Math.floor(Math.sqrt(size) * 2)));
|
|
520
|
+
}
|
|
521
|
+
indexSQL = `
|
|
522
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
475
523
|
ON ${indexName}
|
|
476
524
|
USING ivfflat (embedding ${metricOp})
|
|
477
525
|
WITH (lists = ${lists});
|
|
478
526
|
`;
|
|
527
|
+
}
|
|
528
|
+
await client.query(indexSQL);
|
|
529
|
+
this.indexCache.delete(indexName);
|
|
530
|
+
} finally {
|
|
531
|
+
await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
479
532
|
}
|
|
480
|
-
await client.query(indexSQL);
|
|
481
|
-
this.indexCache.delete(indexName);
|
|
482
533
|
} finally {
|
|
483
534
|
client.release();
|
|
484
535
|
}
|
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.3",
|
|
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
|
+
}
|
|
220
245
|
|
|
221
|
-
|
|
222
|
-
|
|
246
|
+
try {
|
|
247
|
+
// Try to create extension
|
|
248
|
+
await client.query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
249
|
+
|
|
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,6 +256,10 @@ 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
265
|
await this.buildIndex({ indexName, metric, indexConfig });
|
|
@@ -260,20 +293,53 @@ export class PgVector extends MastraVector {
|
|
|
260
293
|
|
|
261
294
|
const client = await this.pool.connect();
|
|
262
295
|
try {
|
|
263
|
-
|
|
296
|
+
// Use a different hash prefix for buildIndex locks to avoid conflicts with createIndex
|
|
297
|
+
const hash = createHash('sha256')
|
|
298
|
+
.update('build:' + indexName)
|
|
299
|
+
.digest('hex');
|
|
300
|
+
const lockId = BigInt('0x' + hash.slice(0, 8)) % BigInt(2 ** 31);
|
|
301
|
+
const acquired = await client.query('SELECT pg_try_advisory_lock($1)', [lockId]);
|
|
302
|
+
|
|
303
|
+
if (!acquired.rows[0].pg_try_advisory_lock) {
|
|
304
|
+
// Check if index already exists
|
|
305
|
+
const exists = await client.query(
|
|
306
|
+
`
|
|
307
|
+
SELECT 1 FROM pg_class c
|
|
308
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
309
|
+
WHERE c.relname = $1
|
|
310
|
+
AND n.nspname = 'public'
|
|
311
|
+
`,
|
|
312
|
+
[`${indexName}_vector_idx`],
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
if (exists.rows.length > 0) {
|
|
316
|
+
console.log(`Index ${indexName}_vector_idx already exists, skipping creation`);
|
|
317
|
+
this.indexCache.delete(indexName); // Still clear cache since we checked
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Index doesn't exist, wait for lock
|
|
322
|
+
await client.query('SELECT pg_advisory_lock($1)', [lockId]);
|
|
323
|
+
}
|
|
264
324
|
|
|
265
|
-
|
|
325
|
+
try {
|
|
326
|
+
await client.query(`DROP INDEX IF EXISTS ${indexName}_vector_idx`);
|
|
327
|
+
|
|
328
|
+
if (indexConfig.type === 'flat') {
|
|
329
|
+
this.indexCache.delete(indexName);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
266
332
|
|
|
267
|
-
|
|
268
|
-
|
|
333
|
+
const metricOp =
|
|
334
|
+
metric === 'cosine' ? 'vector_cosine_ops' : metric === 'euclidean' ? 'vector_l2_ops' : 'vector_ip_ops';
|
|
269
335
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
336
|
+
let indexSQL: string;
|
|
337
|
+
if (indexConfig.type === 'hnsw') {
|
|
338
|
+
const m = indexConfig.hnsw?.m ?? 8;
|
|
339
|
+
const efConstruction = indexConfig.hnsw?.efConstruction ?? 32;
|
|
274
340
|
|
|
275
|
-
|
|
276
|
-
CREATE INDEX ${indexName}_vector_idx
|
|
341
|
+
indexSQL = `
|
|
342
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
277
343
|
ON ${indexName}
|
|
278
344
|
USING hnsw (embedding ${metricOp})
|
|
279
345
|
WITH (
|
|
@@ -281,24 +347,27 @@ export class PgVector extends MastraVector {
|
|
|
281
347
|
ef_construction = ${efConstruction}
|
|
282
348
|
)
|
|
283
349
|
`;
|
|
284
|
-
} else {
|
|
285
|
-
let lists: number;
|
|
286
|
-
if (indexConfig.ivf?.lists) {
|
|
287
|
-
lists = indexConfig.ivf.lists;
|
|
288
350
|
} else {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
351
|
+
let lists: number;
|
|
352
|
+
if (indexConfig.ivf?.lists) {
|
|
353
|
+
lists = indexConfig.ivf.lists;
|
|
354
|
+
} else {
|
|
355
|
+
const size = (await client.query(`SELECT COUNT(*) FROM ${indexName}`)).rows[0].count;
|
|
356
|
+
lists = Math.max(100, Math.min(4000, Math.floor(Math.sqrt(size) * 2)));
|
|
357
|
+
}
|
|
358
|
+
indexSQL = `
|
|
359
|
+
CREATE INDEX IF NOT EXISTS ${indexName}_vector_idx
|
|
294
360
|
ON ${indexName}
|
|
295
361
|
USING ivfflat (embedding ${metricOp})
|
|
296
362
|
WITH (lists = ${lists});
|
|
297
363
|
`;
|
|
298
|
-
|
|
364
|
+
}
|
|
299
365
|
|
|
300
|
-
|
|
301
|
-
|
|
366
|
+
await client.query(indexSQL);
|
|
367
|
+
this.indexCache.delete(indexName);
|
|
368
|
+
} finally {
|
|
369
|
+
await client.query('SELECT pg_advisory_unlock($1)', [lockId]);
|
|
370
|
+
}
|
|
302
371
|
} finally {
|
|
303
372
|
client.release();
|
|
304
373
|
}
|