@mastra/pg 0.14.5 → 0.14.6-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/operations/index.d.ts.map +1 -1
- package/package.json +18 -5
- package/.turbo/turbo-build.log +0 -4
- package/docker-compose.perf.yaml +0 -21
- package/docker-compose.yaml +0 -14
- package/eslint.config.js +0 -6
- package/src/index.ts +0 -3
- package/src/storage/domains/legacy-evals/index.ts +0 -151
- package/src/storage/domains/memory/index.ts +0 -1028
- package/src/storage/domains/operations/index.ts +0 -368
- package/src/storage/domains/scores/index.ts +0 -297
- package/src/storage/domains/traces/index.ts +0 -160
- package/src/storage/domains/utils.ts +0 -12
- package/src/storage/domains/workflows/index.ts +0 -291
- package/src/storage/index.test.ts +0 -11
- package/src/storage/index.ts +0 -514
- package/src/storage/test-utils.ts +0 -377
- package/src/vector/filter.test.ts +0 -967
- package/src/vector/filter.ts +0 -136
- package/src/vector/index.test.ts +0 -2729
- package/src/vector/index.ts +0 -926
- package/src/vector/performance.helpers.ts +0 -286
- package/src/vector/prompt.ts +0 -101
- package/src/vector/sql-builder.ts +0 -358
- package/src/vector/types.ts +0 -16
- package/src/vector/vector.performance.test.ts +0 -367
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -17
- package/vitest.config.ts +0 -12
- package/vitest.perf.config.ts +0 -8
package/src/vector/index.ts
DELETED
|
@@ -1,926 +0,0 @@
|
|
|
1
|
-
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
2
|
-
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
3
|
-
import { MastraVector } from '@mastra/core/vector';
|
|
4
|
-
import type {
|
|
5
|
-
IndexStats,
|
|
6
|
-
QueryResult,
|
|
7
|
-
QueryVectorParams,
|
|
8
|
-
CreateIndexParams,
|
|
9
|
-
UpsertVectorParams,
|
|
10
|
-
DescribeIndexParams,
|
|
11
|
-
DeleteIndexParams,
|
|
12
|
-
DeleteVectorParams,
|
|
13
|
-
UpdateVectorParams,
|
|
14
|
-
} from '@mastra/core/vector';
|
|
15
|
-
import { Mutex } from 'async-mutex';
|
|
16
|
-
import pg from 'pg';
|
|
17
|
-
import xxhash from 'xxhash-wasm';
|
|
18
|
-
|
|
19
|
-
import { PGFilterTranslator } from './filter';
|
|
20
|
-
import type { PGVectorFilter } from './filter';
|
|
21
|
-
import { buildFilterQuery } from './sql-builder';
|
|
22
|
-
import type { IndexConfig, IndexType } from './types';
|
|
23
|
-
|
|
24
|
-
export interface PGIndexStats extends IndexStats {
|
|
25
|
-
type: IndexType;
|
|
26
|
-
config: {
|
|
27
|
-
m?: number;
|
|
28
|
-
efConstruction?: number;
|
|
29
|
-
lists?: number;
|
|
30
|
-
probes?: number;
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface PgQueryVectorParams extends QueryVectorParams<PGVectorFilter> {
|
|
35
|
-
minScore?: number;
|
|
36
|
-
/**
|
|
37
|
-
* HNSW search parameter. Controls the size of the dynamic candidate
|
|
38
|
-
* list during search. Higher values improve accuracy at the cost of speed.
|
|
39
|
-
*/
|
|
40
|
-
ef?: number;
|
|
41
|
-
/**
|
|
42
|
-
* IVFFlat probe parameter. Number of cells to visit during search.
|
|
43
|
-
* Higher values improve accuracy at the cost of speed.
|
|
44
|
-
*/
|
|
45
|
-
probes?: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface PgCreateIndexParams extends CreateIndexParams {
|
|
49
|
-
indexConfig?: IndexConfig;
|
|
50
|
-
buildIndex?: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface PgDefineIndexParams {
|
|
54
|
-
indexName: string;
|
|
55
|
-
metric: 'cosine' | 'euclidean' | 'dotproduct';
|
|
56
|
-
indexConfig: IndexConfig;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export class PgVector extends MastraVector<PGVectorFilter> {
|
|
60
|
-
public pool: pg.Pool;
|
|
61
|
-
private describeIndexCache: Map<string, PGIndexStats> = new Map();
|
|
62
|
-
private createdIndexes = new Map<string, number>();
|
|
63
|
-
private mutexesByName = new Map<string, Mutex>();
|
|
64
|
-
private schema?: string;
|
|
65
|
-
private setupSchemaPromise: Promise<void> | null = null;
|
|
66
|
-
private installVectorExtensionPromise: Promise<void> | null = null;
|
|
67
|
-
private vectorExtensionInstalled: boolean | undefined = undefined;
|
|
68
|
-
private schemaSetupComplete: boolean | undefined = undefined;
|
|
69
|
-
|
|
70
|
-
constructor({
|
|
71
|
-
connectionString,
|
|
72
|
-
schemaName,
|
|
73
|
-
pgPoolOptions,
|
|
74
|
-
}: {
|
|
75
|
-
connectionString: string;
|
|
76
|
-
schemaName?: string;
|
|
77
|
-
pgPoolOptions?: Omit<pg.PoolConfig, 'connectionString'>;
|
|
78
|
-
}) {
|
|
79
|
-
try {
|
|
80
|
-
if (!connectionString || connectionString.trim() === '') {
|
|
81
|
-
throw new Error(
|
|
82
|
-
'PgVector: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.',
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
super();
|
|
86
|
-
|
|
87
|
-
this.schema = schemaName;
|
|
88
|
-
|
|
89
|
-
const basePool = new pg.Pool({
|
|
90
|
-
connectionString,
|
|
91
|
-
max: 20, // Maximum number of clients in the pool
|
|
92
|
-
idleTimeoutMillis: 30000, // Close idle connections after 30 seconds
|
|
93
|
-
connectionTimeoutMillis: 2000, // Fail fast if can't connect
|
|
94
|
-
...pgPoolOptions,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const telemetry = this.__getTelemetry();
|
|
98
|
-
|
|
99
|
-
this.pool =
|
|
100
|
-
telemetry?.traceClass(basePool, {
|
|
101
|
-
spanNamePrefix: 'pg-vector',
|
|
102
|
-
attributes: {
|
|
103
|
-
'vector.type': 'postgres',
|
|
104
|
-
},
|
|
105
|
-
}) ?? basePool;
|
|
106
|
-
|
|
107
|
-
void (async () => {
|
|
108
|
-
// warm the created indexes cache so we don't need to check if indexes exist every time
|
|
109
|
-
const existingIndexes = await this.listIndexes();
|
|
110
|
-
void existingIndexes.map(async indexName => {
|
|
111
|
-
const info = await this.getIndexInfo({ indexName });
|
|
112
|
-
const key = await this.getIndexCacheKey({
|
|
113
|
-
indexName,
|
|
114
|
-
metric: info.metric,
|
|
115
|
-
dimension: info.dimension,
|
|
116
|
-
type: info.type,
|
|
117
|
-
});
|
|
118
|
-
this.createdIndexes.set(indexName, key);
|
|
119
|
-
});
|
|
120
|
-
})();
|
|
121
|
-
} catch (error) {
|
|
122
|
-
throw new MastraError(
|
|
123
|
-
{
|
|
124
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_INITIALIZATION_FAILED',
|
|
125
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
126
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
127
|
-
details: {
|
|
128
|
-
schemaName: schemaName ?? '',
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
error,
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
private getMutexByName(indexName: string) {
|
|
137
|
-
if (!this.mutexesByName.has(indexName)) this.mutexesByName.set(indexName, new Mutex());
|
|
138
|
-
return this.mutexesByName.get(indexName)!;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private getTableName(indexName: string) {
|
|
142
|
-
const parsedIndexName = parseSqlIdentifier(indexName, 'index name');
|
|
143
|
-
const quotedIndexName = `"${parsedIndexName}"`;
|
|
144
|
-
const quotedSchemaName = this.getSchemaName();
|
|
145
|
-
const quotedVectorName = `"${parsedIndexName}_vector_idx"`;
|
|
146
|
-
return {
|
|
147
|
-
tableName: quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName,
|
|
148
|
-
vectorIndexName: quotedVectorName,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
private getSchemaName() {
|
|
153
|
-
return this.schema ? `"${parseSqlIdentifier(this.schema, 'schema name')}"` : undefined;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
transformFilter(filter?: PGVectorFilter) {
|
|
157
|
-
const translator = new PGFilterTranslator();
|
|
158
|
-
return translator.translate(filter);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async getIndexInfo({ indexName }: DescribeIndexParams): Promise<PGIndexStats> {
|
|
162
|
-
if (!this.describeIndexCache.has(indexName)) {
|
|
163
|
-
this.describeIndexCache.set(indexName, await this.describeIndex({ indexName }));
|
|
164
|
-
}
|
|
165
|
-
return this.describeIndexCache.get(indexName)!;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async query({
|
|
169
|
-
indexName,
|
|
170
|
-
queryVector,
|
|
171
|
-
topK = 10,
|
|
172
|
-
filter,
|
|
173
|
-
includeVector = false,
|
|
174
|
-
minScore = -1,
|
|
175
|
-
ef,
|
|
176
|
-
probes,
|
|
177
|
-
}: PgQueryVectorParams): Promise<QueryResult[]> {
|
|
178
|
-
try {
|
|
179
|
-
if (!Number.isInteger(topK) || topK <= 0) {
|
|
180
|
-
throw new Error('topK must be a positive integer');
|
|
181
|
-
}
|
|
182
|
-
if (!Array.isArray(queryVector) || !queryVector.every(x => typeof x === 'number' && Number.isFinite(x))) {
|
|
183
|
-
throw new Error('queryVector must be an array of finite numbers');
|
|
184
|
-
}
|
|
185
|
-
} catch (error) {
|
|
186
|
-
const mastraError = new MastraError(
|
|
187
|
-
{
|
|
188
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_QUERY_INVALID_INPUT',
|
|
189
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
190
|
-
category: ErrorCategory.USER,
|
|
191
|
-
details: {
|
|
192
|
-
indexName,
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
error,
|
|
196
|
-
);
|
|
197
|
-
this.logger?.trackException(mastraError);
|
|
198
|
-
throw mastraError;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const client = await this.pool.connect();
|
|
202
|
-
try {
|
|
203
|
-
await client.query('BEGIN');
|
|
204
|
-
const vectorStr = `[${queryVector.join(',')}]`;
|
|
205
|
-
const translatedFilter = this.transformFilter(filter);
|
|
206
|
-
const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter, minScore, topK);
|
|
207
|
-
|
|
208
|
-
// Get index type and configuration
|
|
209
|
-
const indexInfo = await this.getIndexInfo({ indexName });
|
|
210
|
-
|
|
211
|
-
// Set HNSW search parameter if applicable
|
|
212
|
-
if (indexInfo.type === 'hnsw') {
|
|
213
|
-
// Calculate ef and clamp between 1 and 1000
|
|
214
|
-
const calculatedEf = ef ?? Math.max(topK, (indexInfo?.config?.m ?? 16) * topK);
|
|
215
|
-
const searchEf = Math.min(1000, Math.max(1, calculatedEf));
|
|
216
|
-
await client.query(`SET LOCAL hnsw.ef_search = ${searchEf}`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (indexInfo.type === 'ivfflat' && probes) {
|
|
220
|
-
await client.query(`SET LOCAL ivfflat.probes = ${probes}`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const { tableName } = this.getTableName(indexName);
|
|
224
|
-
|
|
225
|
-
const query = `
|
|
226
|
-
WITH vector_scores AS (
|
|
227
|
-
SELECT
|
|
228
|
-
vector_id as id,
|
|
229
|
-
1 - (embedding <=> '${vectorStr}'::vector) as score,
|
|
230
|
-
metadata
|
|
231
|
-
${includeVector ? ', embedding' : ''}
|
|
232
|
-
FROM ${tableName}
|
|
233
|
-
${filterQuery}
|
|
234
|
-
)
|
|
235
|
-
SELECT *
|
|
236
|
-
FROM vector_scores
|
|
237
|
-
WHERE score > $1
|
|
238
|
-
ORDER BY score DESC
|
|
239
|
-
LIMIT $2`;
|
|
240
|
-
const result = await client.query(query, filterValues);
|
|
241
|
-
await client.query('COMMIT');
|
|
242
|
-
|
|
243
|
-
return result.rows.map(({ id, score, metadata, embedding }) => ({
|
|
244
|
-
id,
|
|
245
|
-
score,
|
|
246
|
-
metadata,
|
|
247
|
-
...(includeVector && embedding && { vector: JSON.parse(embedding) }),
|
|
248
|
-
}));
|
|
249
|
-
} catch (error) {
|
|
250
|
-
await client.query('ROLLBACK');
|
|
251
|
-
const mastraError = new MastraError(
|
|
252
|
-
{
|
|
253
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_QUERY_FAILED',
|
|
254
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
255
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
256
|
-
details: {
|
|
257
|
-
indexName,
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
error,
|
|
261
|
-
);
|
|
262
|
-
this.logger?.trackException(mastraError);
|
|
263
|
-
throw mastraError;
|
|
264
|
-
} finally {
|
|
265
|
-
client.release();
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async upsert({ indexName, vectors, metadata, ids }: UpsertVectorParams): Promise<string[]> {
|
|
270
|
-
const { tableName } = this.getTableName(indexName);
|
|
271
|
-
|
|
272
|
-
// Start a transaction
|
|
273
|
-
const client = await this.pool.connect();
|
|
274
|
-
try {
|
|
275
|
-
await client.query('BEGIN');
|
|
276
|
-
const vectorIds = ids || vectors.map(() => crypto.randomUUID());
|
|
277
|
-
|
|
278
|
-
for (let i = 0; i < vectors.length; i++) {
|
|
279
|
-
const query = `
|
|
280
|
-
INSERT INTO ${tableName} (vector_id, embedding, metadata)
|
|
281
|
-
VALUES ($1, $2::vector, $3::jsonb)
|
|
282
|
-
ON CONFLICT (vector_id)
|
|
283
|
-
DO UPDATE SET
|
|
284
|
-
embedding = $2::vector,
|
|
285
|
-
metadata = $3::jsonb
|
|
286
|
-
RETURNING embedding::text
|
|
287
|
-
`;
|
|
288
|
-
|
|
289
|
-
await client.query(query, [vectorIds[i], `[${vectors[i]?.join(',')}]`, JSON.stringify(metadata?.[i] || {})]);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
await client.query('COMMIT');
|
|
293
|
-
return vectorIds;
|
|
294
|
-
} catch (error) {
|
|
295
|
-
await client.query('ROLLBACK');
|
|
296
|
-
if (error instanceof Error && error.message?.includes('expected') && error.message?.includes('dimensions')) {
|
|
297
|
-
const match = error.message.match(/expected (\d+) dimensions, not (\d+)/);
|
|
298
|
-
if (match) {
|
|
299
|
-
const [, expected, actual] = match;
|
|
300
|
-
const mastraError = new MastraError(
|
|
301
|
-
{
|
|
302
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_UPSERT_INVALID_INPUT',
|
|
303
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
304
|
-
category: ErrorCategory.USER,
|
|
305
|
-
text:
|
|
306
|
-
`Vector dimension mismatch: Index "${indexName}" expects ${expected} dimensions but got ${actual} dimensions. ` +
|
|
307
|
-
`Either use a matching embedding model or delete and recreate the index with the new dimension.`,
|
|
308
|
-
details: {
|
|
309
|
-
indexName,
|
|
310
|
-
expected: expected ?? '',
|
|
311
|
-
actual: actual ?? '',
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
error,
|
|
315
|
-
);
|
|
316
|
-
this.logger?.trackException(mastraError);
|
|
317
|
-
throw mastraError;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const mastraError = new MastraError(
|
|
322
|
-
{
|
|
323
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_UPSERT_FAILED',
|
|
324
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
325
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
326
|
-
details: {
|
|
327
|
-
indexName,
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
error,
|
|
331
|
-
);
|
|
332
|
-
this.logger?.trackException(mastraError);
|
|
333
|
-
throw mastraError;
|
|
334
|
-
} finally {
|
|
335
|
-
client.release();
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
private hasher = xxhash();
|
|
340
|
-
private async getIndexCacheKey({
|
|
341
|
-
indexName,
|
|
342
|
-
dimension,
|
|
343
|
-
metric,
|
|
344
|
-
type,
|
|
345
|
-
}: CreateIndexParams & { type: IndexType | undefined }) {
|
|
346
|
-
const input = indexName + dimension + metric + (type || 'ivfflat'); // ivfflat is default
|
|
347
|
-
return (await this.hasher).h32(input);
|
|
348
|
-
}
|
|
349
|
-
private cachedIndexExists(indexName: string, newKey: number) {
|
|
350
|
-
const existingIndexCacheKey = this.createdIndexes.get(indexName);
|
|
351
|
-
return existingIndexCacheKey && existingIndexCacheKey === newKey;
|
|
352
|
-
}
|
|
353
|
-
private async setupSchema(client: pg.PoolClient) {
|
|
354
|
-
if (!this.schema || this.schemaSetupComplete) {
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (!this.setupSchemaPromise) {
|
|
359
|
-
this.setupSchemaPromise = (async () => {
|
|
360
|
-
try {
|
|
361
|
-
// First check if schema exists and we have usage permission
|
|
362
|
-
const schemaCheck = await client.query(
|
|
363
|
-
`
|
|
364
|
-
SELECT EXISTS (
|
|
365
|
-
SELECT 1 FROM information_schema.schemata
|
|
366
|
-
WHERE schema_name = $1
|
|
367
|
-
)
|
|
368
|
-
`,
|
|
369
|
-
[this.schema],
|
|
370
|
-
);
|
|
371
|
-
|
|
372
|
-
const schemaExists = schemaCheck.rows[0].exists;
|
|
373
|
-
|
|
374
|
-
if (!schemaExists) {
|
|
375
|
-
try {
|
|
376
|
-
await client.query(`CREATE SCHEMA IF NOT EXISTS ${this.getSchemaName()}`);
|
|
377
|
-
this.logger.info(`Schema "${this.schema}" created successfully`);
|
|
378
|
-
} catch (error) {
|
|
379
|
-
this.logger.error(`Failed to create schema "${this.schema}"`, { error });
|
|
380
|
-
throw new Error(
|
|
381
|
-
`Unable to create schema "${this.schema}". This requires CREATE privilege on the database. ` +
|
|
382
|
-
`Either create the schema manually or grant CREATE privilege to the user.`,
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// If we got here, schema exists and we can use it
|
|
388
|
-
this.schemaSetupComplete = true;
|
|
389
|
-
this.logger.debug(`Schema "${this.schema}" is ready for use`);
|
|
390
|
-
} catch (error) {
|
|
391
|
-
// Reset flags so we can retry
|
|
392
|
-
this.schemaSetupComplete = undefined;
|
|
393
|
-
this.setupSchemaPromise = null;
|
|
394
|
-
throw error;
|
|
395
|
-
} finally {
|
|
396
|
-
this.setupSchemaPromise = null;
|
|
397
|
-
}
|
|
398
|
-
})();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
await this.setupSchemaPromise;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
async createIndex({
|
|
405
|
-
indexName,
|
|
406
|
-
dimension,
|
|
407
|
-
metric = 'cosine',
|
|
408
|
-
indexConfig = {},
|
|
409
|
-
buildIndex = true,
|
|
410
|
-
}: PgCreateIndexParams): Promise<void> {
|
|
411
|
-
const { tableName } = this.getTableName(indexName);
|
|
412
|
-
|
|
413
|
-
// Validate inputs
|
|
414
|
-
try {
|
|
415
|
-
if (!indexName.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
|
|
416
|
-
throw new Error('Invalid index name format');
|
|
417
|
-
}
|
|
418
|
-
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
419
|
-
throw new Error('Dimension must be a positive integer');
|
|
420
|
-
}
|
|
421
|
-
} catch (error) {
|
|
422
|
-
const mastraError = new MastraError(
|
|
423
|
-
{
|
|
424
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_CREATE_INDEX_INVALID_INPUT',
|
|
425
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
426
|
-
category: ErrorCategory.USER,
|
|
427
|
-
details: {
|
|
428
|
-
indexName,
|
|
429
|
-
},
|
|
430
|
-
},
|
|
431
|
-
error,
|
|
432
|
-
);
|
|
433
|
-
this.logger?.trackException(mastraError);
|
|
434
|
-
throw mastraError;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const indexCacheKey = await this.getIndexCacheKey({ indexName, dimension, type: indexConfig.type, metric });
|
|
438
|
-
if (this.cachedIndexExists(indexName, indexCacheKey)) {
|
|
439
|
-
// we already saw this index get created since the process started, no need to recreate it
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const mutex = this.getMutexByName(`create-${indexName}`);
|
|
444
|
-
// Use async-mutex instead of advisory lock for perf (over 2x as fast)
|
|
445
|
-
await mutex
|
|
446
|
-
.runExclusive(async () => {
|
|
447
|
-
if (this.cachedIndexExists(indexName, indexCacheKey)) {
|
|
448
|
-
// this may have been created while we were waiting to acquire a lock
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const client = await this.pool.connect();
|
|
453
|
-
|
|
454
|
-
try {
|
|
455
|
-
// Setup schema if needed
|
|
456
|
-
await this.setupSchema(client);
|
|
457
|
-
|
|
458
|
-
// Install vector extension first (needs to be in public schema)
|
|
459
|
-
await this.installVectorExtension(client);
|
|
460
|
-
await client.query(`
|
|
461
|
-
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
462
|
-
id SERIAL PRIMARY KEY,
|
|
463
|
-
vector_id TEXT UNIQUE NOT NULL,
|
|
464
|
-
embedding vector(${dimension}),
|
|
465
|
-
metadata JSONB DEFAULT '{}'::jsonb
|
|
466
|
-
);
|
|
467
|
-
`);
|
|
468
|
-
this.createdIndexes.set(indexName, indexCacheKey);
|
|
469
|
-
|
|
470
|
-
if (buildIndex) {
|
|
471
|
-
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
472
|
-
}
|
|
473
|
-
} catch (error: any) {
|
|
474
|
-
this.createdIndexes.delete(indexName);
|
|
475
|
-
throw error;
|
|
476
|
-
} finally {
|
|
477
|
-
client.release();
|
|
478
|
-
}
|
|
479
|
-
})
|
|
480
|
-
.catch(error => {
|
|
481
|
-
const mastraError = new MastraError(
|
|
482
|
-
{
|
|
483
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_CREATE_INDEX_FAILED',
|
|
484
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
485
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
486
|
-
details: {
|
|
487
|
-
indexName,
|
|
488
|
-
},
|
|
489
|
-
},
|
|
490
|
-
error,
|
|
491
|
-
);
|
|
492
|
-
this.logger?.trackException(mastraError);
|
|
493
|
-
throw mastraError;
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
async buildIndex({ indexName, metric = 'cosine', indexConfig }: PgDefineIndexParams): Promise<void> {
|
|
498
|
-
const client = await this.pool.connect();
|
|
499
|
-
try {
|
|
500
|
-
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
501
|
-
} catch (error: any) {
|
|
502
|
-
const mastraError = new MastraError(
|
|
503
|
-
{
|
|
504
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_BUILD_INDEX_FAILED',
|
|
505
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
506
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
507
|
-
details: {
|
|
508
|
-
indexName,
|
|
509
|
-
},
|
|
510
|
-
},
|
|
511
|
-
error,
|
|
512
|
-
);
|
|
513
|
-
this.logger?.trackException(mastraError);
|
|
514
|
-
throw mastraError;
|
|
515
|
-
} finally {
|
|
516
|
-
client.release();
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
private async setupIndex({ indexName, metric, indexConfig }: PgDefineIndexParams, client: pg.PoolClient) {
|
|
521
|
-
const mutex = this.getMutexByName(`build-${indexName}`);
|
|
522
|
-
// Use async-mutex instead of advisory lock for perf (over 2x as fast)
|
|
523
|
-
await mutex.runExclusive(async () => {
|
|
524
|
-
const { tableName, vectorIndexName } = this.getTableName(indexName);
|
|
525
|
-
|
|
526
|
-
if (this.createdIndexes.has(indexName)) {
|
|
527
|
-
await client.query(`DROP INDEX IF EXISTS ${vectorIndexName}`);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
if (indexConfig.type === 'flat') {
|
|
531
|
-
this.describeIndexCache.delete(indexName);
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const metricOp =
|
|
536
|
-
metric === 'cosine' ? 'vector_cosine_ops' : metric === 'euclidean' ? 'vector_l2_ops' : 'vector_ip_ops';
|
|
537
|
-
|
|
538
|
-
let indexSQL: string;
|
|
539
|
-
if (indexConfig.type === 'hnsw') {
|
|
540
|
-
const m = indexConfig.hnsw?.m ?? 8;
|
|
541
|
-
const efConstruction = indexConfig.hnsw?.efConstruction ?? 32;
|
|
542
|
-
|
|
543
|
-
indexSQL = `
|
|
544
|
-
CREATE INDEX IF NOT EXISTS ${vectorIndexName}
|
|
545
|
-
ON ${tableName}
|
|
546
|
-
USING hnsw (embedding ${metricOp})
|
|
547
|
-
WITH (
|
|
548
|
-
m = ${m},
|
|
549
|
-
ef_construction = ${efConstruction}
|
|
550
|
-
)
|
|
551
|
-
`;
|
|
552
|
-
} else {
|
|
553
|
-
let lists: number;
|
|
554
|
-
if (indexConfig.ivf?.lists) {
|
|
555
|
-
lists = indexConfig.ivf.lists;
|
|
556
|
-
} else {
|
|
557
|
-
const size = (await client.query(`SELECT COUNT(*) FROM ${tableName}`)).rows[0].count;
|
|
558
|
-
lists = Math.max(100, Math.min(4000, Math.floor(Math.sqrt(size) * 2)));
|
|
559
|
-
}
|
|
560
|
-
indexSQL = `
|
|
561
|
-
CREATE INDEX IF NOT EXISTS ${vectorIndexName}
|
|
562
|
-
ON ${tableName}
|
|
563
|
-
USING ivfflat (embedding ${metricOp})
|
|
564
|
-
WITH (lists = ${lists});
|
|
565
|
-
`;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
await client.query(indexSQL);
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
private async installVectorExtension(client: pg.PoolClient) {
|
|
573
|
-
// If we've already successfully installed, no need to do anything
|
|
574
|
-
if (this.vectorExtensionInstalled) {
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// If there's no existing installation attempt or the previous one failed
|
|
579
|
-
if (!this.installVectorExtensionPromise) {
|
|
580
|
-
this.installVectorExtensionPromise = (async () => {
|
|
581
|
-
try {
|
|
582
|
-
// First check if extension is already installed
|
|
583
|
-
const extensionCheck = await client.query(`
|
|
584
|
-
SELECT EXISTS (
|
|
585
|
-
SELECT 1 FROM pg_extension WHERE extname = 'vector'
|
|
586
|
-
);
|
|
587
|
-
`);
|
|
588
|
-
|
|
589
|
-
this.vectorExtensionInstalled = extensionCheck.rows[0].exists;
|
|
590
|
-
|
|
591
|
-
if (!this.vectorExtensionInstalled) {
|
|
592
|
-
try {
|
|
593
|
-
await client.query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
594
|
-
this.vectorExtensionInstalled = true;
|
|
595
|
-
this.logger.info('Vector extension installed successfully');
|
|
596
|
-
} catch {
|
|
597
|
-
this.logger.warn(
|
|
598
|
-
'Could not install vector extension. This requires superuser privileges. ' +
|
|
599
|
-
'If the extension is already installed globally, you can ignore this warning.',
|
|
600
|
-
);
|
|
601
|
-
// Don't set vectorExtensionInstalled to false here since we're not sure if it failed
|
|
602
|
-
// due to permissions or if it's already installed globally
|
|
603
|
-
}
|
|
604
|
-
} else {
|
|
605
|
-
this.logger.debug('Vector extension already installed, skipping installation');
|
|
606
|
-
}
|
|
607
|
-
} catch (error) {
|
|
608
|
-
this.logger.error('Error checking vector extension status', { error });
|
|
609
|
-
// Reset both the promise and the flag so we can retry
|
|
610
|
-
this.vectorExtensionInstalled = undefined;
|
|
611
|
-
this.installVectorExtensionPromise = null;
|
|
612
|
-
throw error; // Re-throw so caller knows it failed
|
|
613
|
-
} finally {
|
|
614
|
-
// Clear the promise after completion (success or failure)
|
|
615
|
-
this.installVectorExtensionPromise = null;
|
|
616
|
-
}
|
|
617
|
-
})();
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Wait for the installation process to complete
|
|
621
|
-
await this.installVectorExtensionPromise;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
async listIndexes(): Promise<string[]> {
|
|
625
|
-
const client = await this.pool.connect();
|
|
626
|
-
try {
|
|
627
|
-
// Then let's see which ones have vector columns
|
|
628
|
-
const vectorTablesQuery = `
|
|
629
|
-
SELECT DISTINCT table_name
|
|
630
|
-
FROM information_schema.columns
|
|
631
|
-
WHERE table_schema = $1
|
|
632
|
-
AND udt_name = 'vector';
|
|
633
|
-
`;
|
|
634
|
-
const vectorTables = await client.query(vectorTablesQuery, [this.schema || 'public']);
|
|
635
|
-
return vectorTables.rows.map(row => row.table_name);
|
|
636
|
-
} catch (e) {
|
|
637
|
-
const mastraError = new MastraError(
|
|
638
|
-
{
|
|
639
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_LIST_INDEXES_FAILED',
|
|
640
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
641
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
642
|
-
},
|
|
643
|
-
e,
|
|
644
|
-
);
|
|
645
|
-
this.logger?.trackException(mastraError);
|
|
646
|
-
throw mastraError;
|
|
647
|
-
} finally {
|
|
648
|
-
client.release();
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* Retrieves statistics about a vector index.
|
|
654
|
-
*
|
|
655
|
-
* @param {string} indexName - The name of the index to describe
|
|
656
|
-
* @returns A promise that resolves to the index statistics including dimension, count and metric
|
|
657
|
-
*/
|
|
658
|
-
async describeIndex({ indexName }: DescribeIndexParams): Promise<PGIndexStats> {
|
|
659
|
-
const client = await this.pool.connect();
|
|
660
|
-
try {
|
|
661
|
-
const { tableName } = this.getTableName(indexName);
|
|
662
|
-
|
|
663
|
-
// Check if table exists with a vector column
|
|
664
|
-
const tableExistsQuery = `
|
|
665
|
-
SELECT 1
|
|
666
|
-
FROM information_schema.columns
|
|
667
|
-
WHERE table_schema = $1
|
|
668
|
-
AND table_name = $2
|
|
669
|
-
AND udt_name = 'vector'
|
|
670
|
-
LIMIT 1;
|
|
671
|
-
`;
|
|
672
|
-
const tableExists = await client.query(tableExistsQuery, [this.schema || 'public', indexName]);
|
|
673
|
-
|
|
674
|
-
if (tableExists.rows.length === 0) {
|
|
675
|
-
throw new Error(`Vector table ${tableName} does not exist`);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Get vector dimension
|
|
679
|
-
const dimensionQuery = `
|
|
680
|
-
SELECT atttypmod as dimension
|
|
681
|
-
FROM pg_attribute
|
|
682
|
-
WHERE attrelid = $1::regclass
|
|
683
|
-
AND attname = 'embedding';
|
|
684
|
-
`;
|
|
685
|
-
|
|
686
|
-
// Get row count
|
|
687
|
-
const countQuery = `
|
|
688
|
-
SELECT COUNT(*) as count
|
|
689
|
-
FROM ${tableName};
|
|
690
|
-
`;
|
|
691
|
-
|
|
692
|
-
// Get index metric type
|
|
693
|
-
const indexQuery = `
|
|
694
|
-
SELECT
|
|
695
|
-
am.amname as index_method,
|
|
696
|
-
pg_get_indexdef(i.indexrelid) as index_def,
|
|
697
|
-
opclass.opcname as operator_class
|
|
698
|
-
FROM pg_index i
|
|
699
|
-
JOIN pg_class c ON i.indexrelid = c.oid
|
|
700
|
-
JOIN pg_am am ON c.relam = am.oid
|
|
701
|
-
JOIN pg_opclass opclass ON i.indclass[0] = opclass.oid
|
|
702
|
-
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
703
|
-
WHERE c.relname = $1
|
|
704
|
-
AND n.nspname = $2;
|
|
705
|
-
`;
|
|
706
|
-
|
|
707
|
-
const [dimResult, countResult, indexResult] = await Promise.all([
|
|
708
|
-
client.query(dimensionQuery, [tableName]),
|
|
709
|
-
client.query(countQuery),
|
|
710
|
-
client.query(indexQuery, [`${indexName}_vector_idx`, this.schema || 'public']),
|
|
711
|
-
]);
|
|
712
|
-
|
|
713
|
-
const { index_method, index_def, operator_class } = indexResult.rows[0] || {
|
|
714
|
-
index_method: 'flat',
|
|
715
|
-
index_def: '',
|
|
716
|
-
operator_class: 'cosine',
|
|
717
|
-
};
|
|
718
|
-
|
|
719
|
-
// Convert pg_vector index method to our metric type
|
|
720
|
-
const metric = operator_class.includes('l2')
|
|
721
|
-
? 'euclidean'
|
|
722
|
-
: operator_class.includes('ip')
|
|
723
|
-
? 'dotproduct'
|
|
724
|
-
: 'cosine';
|
|
725
|
-
|
|
726
|
-
// Parse index configuration
|
|
727
|
-
const config: { m?: number; efConstruction?: number; lists?: number } = {};
|
|
728
|
-
|
|
729
|
-
if (index_method === 'hnsw') {
|
|
730
|
-
const m = index_def.match(/m\s*=\s*'?(\d+)'?/)?.[1];
|
|
731
|
-
const efConstruction = index_def.match(/ef_construction\s*=\s*'?(\d+)'?/)?.[1];
|
|
732
|
-
if (m) config.m = parseInt(m);
|
|
733
|
-
if (efConstruction) config.efConstruction = parseInt(efConstruction);
|
|
734
|
-
} else if (index_method === 'ivfflat') {
|
|
735
|
-
const lists = index_def.match(/lists\s*=\s*'?(\d+)'?/)?.[1];
|
|
736
|
-
if (lists) config.lists = parseInt(lists);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
return {
|
|
740
|
-
dimension: dimResult.rows[0].dimension,
|
|
741
|
-
count: parseInt(countResult.rows[0].count),
|
|
742
|
-
metric,
|
|
743
|
-
type: index_method as 'flat' | 'hnsw' | 'ivfflat',
|
|
744
|
-
config,
|
|
745
|
-
};
|
|
746
|
-
} catch (e: any) {
|
|
747
|
-
await client.query('ROLLBACK');
|
|
748
|
-
const mastraError = new MastraError(
|
|
749
|
-
{
|
|
750
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_DESCRIBE_INDEX_FAILED',
|
|
751
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
752
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
753
|
-
details: {
|
|
754
|
-
indexName,
|
|
755
|
-
},
|
|
756
|
-
},
|
|
757
|
-
e,
|
|
758
|
-
);
|
|
759
|
-
this.logger?.trackException(mastraError);
|
|
760
|
-
throw mastraError;
|
|
761
|
-
} finally {
|
|
762
|
-
client.release();
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
async deleteIndex({ indexName }: DeleteIndexParams): Promise<void> {
|
|
767
|
-
const client = await this.pool.connect();
|
|
768
|
-
try {
|
|
769
|
-
const { tableName } = this.getTableName(indexName);
|
|
770
|
-
// Drop the table
|
|
771
|
-
await client.query(`DROP TABLE IF EXISTS ${tableName} CASCADE`);
|
|
772
|
-
this.createdIndexes.delete(indexName);
|
|
773
|
-
} catch (error: any) {
|
|
774
|
-
await client.query('ROLLBACK');
|
|
775
|
-
const mastraError = new MastraError(
|
|
776
|
-
{
|
|
777
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_DELETE_INDEX_FAILED',
|
|
778
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
779
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
780
|
-
details: {
|
|
781
|
-
indexName,
|
|
782
|
-
},
|
|
783
|
-
},
|
|
784
|
-
error,
|
|
785
|
-
);
|
|
786
|
-
this.logger?.trackException(mastraError);
|
|
787
|
-
throw mastraError;
|
|
788
|
-
} finally {
|
|
789
|
-
client.release();
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
async truncateIndex({ indexName }: DeleteIndexParams): Promise<void> {
|
|
794
|
-
const client = await this.pool.connect();
|
|
795
|
-
try {
|
|
796
|
-
const { tableName } = this.getTableName(indexName);
|
|
797
|
-
await client.query(`TRUNCATE ${tableName}`);
|
|
798
|
-
} catch (e: any) {
|
|
799
|
-
await client.query('ROLLBACK');
|
|
800
|
-
const mastraError = new MastraError(
|
|
801
|
-
{
|
|
802
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_TRUNCATE_INDEX_FAILED',
|
|
803
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
804
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
805
|
-
details: {
|
|
806
|
-
indexName,
|
|
807
|
-
},
|
|
808
|
-
},
|
|
809
|
-
e,
|
|
810
|
-
);
|
|
811
|
-
this.logger?.trackException(mastraError);
|
|
812
|
-
throw mastraError;
|
|
813
|
-
} finally {
|
|
814
|
-
client.release();
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
async disconnect() {
|
|
819
|
-
await this.pool.end();
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* Updates a vector by its ID with the provided vector and/or metadata.
|
|
824
|
-
* @param indexName - The name of the index containing the vector.
|
|
825
|
-
* @param id - The ID of the vector to update.
|
|
826
|
-
* @param update - An object containing the vector and/or metadata to update.
|
|
827
|
-
* @param update.vector - An optional array of numbers representing the new vector.
|
|
828
|
-
* @param update.metadata - An optional record containing the new metadata.
|
|
829
|
-
* @returns A promise that resolves when the update is complete.
|
|
830
|
-
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
831
|
-
*/
|
|
832
|
-
async updateVector({ indexName, id, update }: UpdateVectorParams): Promise<void> {
|
|
833
|
-
let client;
|
|
834
|
-
try {
|
|
835
|
-
if (!update.vector && !update.metadata) {
|
|
836
|
-
throw new Error('No updates provided');
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
client = await this.pool.connect();
|
|
840
|
-
let updateParts = [];
|
|
841
|
-
let values = [id];
|
|
842
|
-
let valueIndex = 2;
|
|
843
|
-
|
|
844
|
-
if (update.vector) {
|
|
845
|
-
updateParts.push(`embedding = $${valueIndex}::vector`);
|
|
846
|
-
values.push(`[${update.vector.join(',')}]`);
|
|
847
|
-
valueIndex++;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
if (update.metadata) {
|
|
851
|
-
updateParts.push(`metadata = $${valueIndex}::jsonb`);
|
|
852
|
-
values.push(JSON.stringify(update.metadata));
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
if (updateParts.length === 0) {
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
const { tableName } = this.getTableName(indexName);
|
|
860
|
-
|
|
861
|
-
// query looks like this:
|
|
862
|
-
// UPDATE table SET embedding = $2::vector, metadata = $3::jsonb WHERE id = $1
|
|
863
|
-
const query = `
|
|
864
|
-
UPDATE ${tableName}
|
|
865
|
-
SET ${updateParts.join(', ')}
|
|
866
|
-
WHERE vector_id = $1
|
|
867
|
-
`;
|
|
868
|
-
|
|
869
|
-
await client.query(query, values);
|
|
870
|
-
} catch (error: any) {
|
|
871
|
-
const mastraError = new MastraError(
|
|
872
|
-
{
|
|
873
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_UPDATE_VECTOR_FAILED',
|
|
874
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
875
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
876
|
-
details: {
|
|
877
|
-
indexName,
|
|
878
|
-
id,
|
|
879
|
-
},
|
|
880
|
-
},
|
|
881
|
-
error,
|
|
882
|
-
);
|
|
883
|
-
this.logger?.trackException(mastraError);
|
|
884
|
-
throw mastraError;
|
|
885
|
-
} finally {
|
|
886
|
-
client?.release();
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
/**
|
|
891
|
-
* Deletes a vector by its ID.
|
|
892
|
-
* @param indexName - The name of the index containing the vector.
|
|
893
|
-
* @param id - The ID of the vector to delete.
|
|
894
|
-
* @returns A promise that resolves when the deletion is complete.
|
|
895
|
-
* @throws Will throw an error if the deletion operation fails.
|
|
896
|
-
*/
|
|
897
|
-
async deleteVector({ indexName, id }: DeleteVectorParams): Promise<void> {
|
|
898
|
-
let client;
|
|
899
|
-
try {
|
|
900
|
-
client = await this.pool.connect();
|
|
901
|
-
const { tableName } = this.getTableName(indexName);
|
|
902
|
-
const query = `
|
|
903
|
-
DELETE FROM ${tableName}
|
|
904
|
-
WHERE vector_id = $1
|
|
905
|
-
`;
|
|
906
|
-
await client.query(query, [id]);
|
|
907
|
-
} catch (error: any) {
|
|
908
|
-
const mastraError = new MastraError(
|
|
909
|
-
{
|
|
910
|
-
id: 'MASTRA_STORAGE_PG_VECTOR_DELETE_VECTOR_FAILED',
|
|
911
|
-
domain: ErrorDomain.MASTRA_VECTOR,
|
|
912
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
913
|
-
details: {
|
|
914
|
-
indexName,
|
|
915
|
-
id,
|
|
916
|
-
},
|
|
917
|
-
},
|
|
918
|
-
error,
|
|
919
|
-
);
|
|
920
|
-
this.logger?.trackException(mastraError);
|
|
921
|
-
throw mastraError;
|
|
922
|
-
} finally {
|
|
923
|
-
client?.release();
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
}
|