@mastra/pg 0.3.4 → 0.4.0-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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +37 -0
- package/dist/_tsup-dts-rollup.d.cts +21 -83
- package/dist/_tsup-dts-rollup.d.ts +21 -83
- package/dist/index.cjs +169 -185
- package/dist/index.js +169 -185
- package/docker-compose.perf.yaml +9 -9
- package/package.json +7 -4
- package/src/storage/index.test.ts +32 -51
- package/src/storage/index.ts +13 -17
- package/src/vector/index.test.ts +52 -179
- package/src/vector/index.ts +64 -152
- package/src/vector/sql-builder.ts +110 -77
- package/src/vector/vector.performance.test.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
|
|
1
2
|
import { MastraVector } from '@mastra/core/vector';
|
|
2
3
|
import { Mutex } from 'async-mutex';
|
|
3
4
|
import pg from 'pg';
|
|
@@ -73,22 +74,26 @@ var PGFilterTranslator = class extends BaseFilterTranslator {
|
|
|
73
74
|
return { $regex: flags ? `(?${flags})${pattern}` : pattern };
|
|
74
75
|
}
|
|
75
76
|
};
|
|
76
|
-
|
|
77
|
-
// src/vector/sql-builder.ts
|
|
78
77
|
var createBasicOperator = (symbol) => {
|
|
79
|
-
return (key, paramIndex) =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
return (key, paramIndex) => {
|
|
79
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
80
|
+
return {
|
|
81
|
+
sql: `CASE
|
|
82
|
+
WHEN $${paramIndex}::text IS NULL THEN metadata#>>'{${jsonPathKey}}' IS ${symbol === "=" ? "" : "NOT"} NULL
|
|
83
|
+
ELSE metadata#>>'{${jsonPathKey}}' ${symbol} $${paramIndex}::text
|
|
84
|
+
END`,
|
|
85
|
+
needsValue: true
|
|
86
|
+
};
|
|
87
|
+
};
|
|
86
88
|
};
|
|
87
89
|
var createNumericOperator = (symbol) => {
|
|
88
|
-
return (key, paramIndex) =>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
return (key, paramIndex) => {
|
|
91
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
92
|
+
return {
|
|
93
|
+
sql: `(metadata#>>'{${jsonPathKey}}')::numeric ${symbol} $${paramIndex}`,
|
|
94
|
+
needsValue: true
|
|
95
|
+
};
|
|
96
|
+
};
|
|
92
97
|
};
|
|
93
98
|
function buildElemMatchConditions(value, paramIndex) {
|
|
94
99
|
if (typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -139,46 +144,56 @@ var FILTER_OPERATORS = {
|
|
|
139
144
|
$lt: createNumericOperator("<"),
|
|
140
145
|
$lte: createNumericOperator("<="),
|
|
141
146
|
// Array Operators
|
|
142
|
-
$in: (key, paramIndex) =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
147
|
+
$in: (key, paramIndex) => {
|
|
148
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
149
|
+
return {
|
|
150
|
+
sql: `(
|
|
151
|
+
CASE
|
|
152
|
+
WHEN jsonb_typeof(metadata->'${jsonPathKey}') = 'array' THEN
|
|
153
|
+
EXISTS (
|
|
154
|
+
SELECT 1 FROM jsonb_array_elements_text(metadata->'${jsonPathKey}') as elem
|
|
155
|
+
WHERE elem = ANY($${paramIndex}::text[])
|
|
156
|
+
)
|
|
157
|
+
ELSE metadata#>>'{${jsonPathKey}}' = ANY($${paramIndex}::text[])
|
|
158
|
+
END
|
|
159
|
+
)`,
|
|
160
|
+
needsValue: true
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
$nin: (key, paramIndex) => {
|
|
164
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
165
|
+
return {
|
|
166
|
+
sql: `(
|
|
167
|
+
CASE
|
|
168
|
+
WHEN jsonb_typeof(metadata->'${jsonPathKey}') = 'array' THEN
|
|
169
|
+
NOT EXISTS (
|
|
170
|
+
SELECT 1 FROM jsonb_array_elements_text(metadata->'${jsonPathKey}') as elem
|
|
171
|
+
WHERE elem = ANY($${paramIndex}::text[])
|
|
172
|
+
)
|
|
173
|
+
ELSE metadata#>>'{${jsonPathKey}}' != ALL($${paramIndex}::text[])
|
|
174
|
+
END
|
|
175
|
+
)`,
|
|
176
|
+
needsValue: true
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
$all: (key, paramIndex) => {
|
|
180
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
181
|
+
return {
|
|
182
|
+
sql: `CASE WHEN array_length($${paramIndex}::text[], 1) IS NULL THEN false
|
|
183
|
+
ELSE (metadata#>'{${jsonPathKey}}')::jsonb ?& $${paramIndex}::text[] END`,
|
|
184
|
+
needsValue: true
|
|
185
|
+
};
|
|
186
|
+
},
|
|
173
187
|
$elemMatch: (key, paramIndex, value) => {
|
|
174
188
|
const { sql, values } = buildElemMatchConditions(value, paramIndex);
|
|
189
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
175
190
|
return {
|
|
176
191
|
sql: `(
|
|
177
192
|
CASE
|
|
178
|
-
WHEN jsonb_typeof(metadata->'${
|
|
193
|
+
WHEN jsonb_typeof(metadata->'${jsonPathKey}') = 'array' THEN
|
|
179
194
|
EXISTS (
|
|
180
195
|
SELECT 1
|
|
181
|
-
FROM jsonb_array_elements(metadata->'${
|
|
196
|
+
FROM jsonb_array_elements(metadata->'${jsonPathKey}') as elem
|
|
182
197
|
WHERE ${sql}
|
|
183
198
|
)
|
|
184
199
|
ELSE FALSE
|
|
@@ -189,33 +204,40 @@ var FILTER_OPERATORS = {
|
|
|
189
204
|
};
|
|
190
205
|
},
|
|
191
206
|
// Element Operators
|
|
192
|
-
$exists: (key) =>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
207
|
+
$exists: (key) => {
|
|
208
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
209
|
+
return {
|
|
210
|
+
sql: `metadata ? '${jsonPathKey}'`,
|
|
211
|
+
needsValue: false
|
|
212
|
+
};
|
|
213
|
+
},
|
|
196
214
|
// Logical Operators
|
|
197
215
|
$and: (key) => ({ sql: `(${key})`, needsValue: false }),
|
|
198
216
|
$or: (key) => ({ sql: `(${key})`, needsValue: false }),
|
|
199
217
|
$not: (key) => ({ sql: `NOT (${key})`, needsValue: false }),
|
|
200
218
|
$nor: (key) => ({ sql: `NOT (${key})`, needsValue: false }),
|
|
201
219
|
// Regex Operators
|
|
202
|
-
$regex: (key, paramIndex) =>
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
220
|
+
$regex: (key, paramIndex) => {
|
|
221
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
222
|
+
return {
|
|
223
|
+
sql: `metadata#>>'{${jsonPathKey}}' ~ $${paramIndex}`,
|
|
224
|
+
needsValue: true
|
|
225
|
+
};
|
|
226
|
+
},
|
|
206
227
|
$contains: (key, paramIndex, value) => {
|
|
228
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
207
229
|
let sql;
|
|
208
230
|
if (Array.isArray(value)) {
|
|
209
|
-
sql = `(metadata->'${
|
|
231
|
+
sql = `(metadata->'${jsonPathKey}') ?& $${paramIndex}`;
|
|
210
232
|
} else if (typeof value === "string") {
|
|
211
|
-
sql = `metadata->>'${
|
|
233
|
+
sql = `metadata->>'${jsonPathKey}' ILIKE '%' || $${paramIndex} || '%' ESCAPE '\\'`;
|
|
212
234
|
} else {
|
|
213
|
-
sql = `metadata->>'${
|
|
235
|
+
sql = `metadata->>'${jsonPathKey}' = $${paramIndex}`;
|
|
214
236
|
}
|
|
215
237
|
return {
|
|
216
238
|
sql,
|
|
217
239
|
needsValue: true,
|
|
218
|
-
transformValue: () => Array.isArray(value) ? value.map(String) : value
|
|
240
|
+
transformValue: () => Array.isArray(value) ? value.map(String) : typeof value === "string" ? escapeLikePattern(value) : value
|
|
219
241
|
};
|
|
220
242
|
},
|
|
221
243
|
/**
|
|
@@ -230,29 +252,36 @@ var FILTER_OPERATORS = {
|
|
|
230
252
|
// return JSON.stringify(parts.reduceRight((value, key) => ({ [key]: value }), value));
|
|
231
253
|
// },
|
|
232
254
|
// }),
|
|
233
|
-
$size: (key, paramIndex) =>
|
|
234
|
-
|
|
255
|
+
$size: (key, paramIndex) => {
|
|
256
|
+
const jsonPathKey = parseJsonPathKey(key);
|
|
257
|
+
return {
|
|
258
|
+
sql: `(
|
|
235
259
|
CASE
|
|
236
|
-
WHEN jsonb_typeof(metadata#>'{${
|
|
237
|
-
jsonb_array_length(metadata#>'{${
|
|
260
|
+
WHEN jsonb_typeof(metadata#>'{${jsonPathKey}}') = 'array' THEN
|
|
261
|
+
jsonb_array_length(metadata#>'{${jsonPathKey}}') = $${paramIndex}
|
|
238
262
|
ELSE FALSE
|
|
239
263
|
END
|
|
240
264
|
)`,
|
|
241
|
-
|
|
242
|
-
|
|
265
|
+
needsValue: true
|
|
266
|
+
};
|
|
267
|
+
}
|
|
243
268
|
};
|
|
244
|
-
var
|
|
245
|
-
|
|
269
|
+
var parseJsonPathKey = (key) => {
|
|
270
|
+
const parsedKey = key !== "" ? parseFieldKey(key) : "";
|
|
271
|
+
return parsedKey.replace(/\./g, ",");
|
|
246
272
|
};
|
|
247
|
-
function
|
|
248
|
-
|
|
273
|
+
function escapeLikePattern(str) {
|
|
274
|
+
return str.replace(/([%_\\])/g, "\\$1");
|
|
275
|
+
}
|
|
276
|
+
function buildFilterQuery(filter, minScore, topK) {
|
|
277
|
+
const values = [minScore, topK];
|
|
249
278
|
function buildCondition(key, value, parentPath) {
|
|
250
279
|
if (["$and", "$or", "$not", "$nor"].includes(key)) {
|
|
251
280
|
return handleLogicalOperator(key, value);
|
|
252
281
|
}
|
|
253
282
|
if (!value || typeof value !== "object") {
|
|
254
283
|
values.push(value);
|
|
255
|
-
return `metadata#>>'{${
|
|
284
|
+
return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
|
|
256
285
|
}
|
|
257
286
|
const [[operator, operatorValue] = []] = Object.entries(value);
|
|
258
287
|
if (operator === "$not") {
|
|
@@ -262,7 +291,7 @@ function buildFilterQuery(filter, minScore) {
|
|
|
262
291
|
throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
|
|
263
292
|
}
|
|
264
293
|
const operatorFn2 = FILTER_OPERATORS[nestedOp];
|
|
265
|
-
const operatorResult2 = operatorFn2(key, values.length + 1);
|
|
294
|
+
const operatorResult2 = operatorFn2(key, values.length + 1, nestedValue);
|
|
266
295
|
if (operatorResult2.needsValue) {
|
|
267
296
|
values.push(nestedValue);
|
|
268
297
|
}
|
|
@@ -333,27 +362,11 @@ var PgVector = class extends MastraVector {
|
|
|
333
362
|
installVectorExtensionPromise = null;
|
|
334
363
|
vectorExtensionInstalled = void 0;
|
|
335
364
|
schemaSetupComplete = void 0;
|
|
336
|
-
constructor(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
console.warn(
|
|
342
|
-
`DEPRECATION WARNING: Passing connectionString as a string to PgVector constructor is deprecated.
|
|
343
|
-
|
|
344
|
-
Please use an object parameter instead:
|
|
345
|
-
new PgVector({ connectionString })
|
|
346
|
-
|
|
347
|
-
The string signature will be removed on May 20th, 2025.`
|
|
348
|
-
);
|
|
349
|
-
connectionString = config;
|
|
350
|
-
schemaName = void 0;
|
|
351
|
-
pgPoolOptions = void 0;
|
|
352
|
-
} else {
|
|
353
|
-
connectionString = config.connectionString;
|
|
354
|
-
schemaName = config.schemaName;
|
|
355
|
-
pgPoolOptions = config.pgPoolOptions;
|
|
356
|
-
}
|
|
365
|
+
constructor({
|
|
366
|
+
connectionString,
|
|
367
|
+
schemaName,
|
|
368
|
+
pgPoolOptions
|
|
369
|
+
}) {
|
|
357
370
|
if (!connectionString || connectionString.trim() === "") {
|
|
358
371
|
throw new Error(
|
|
359
372
|
"PgVector: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults."
|
|
@@ -381,7 +394,7 @@ var PgVector = class extends MastraVector {
|
|
|
381
394
|
void (async () => {
|
|
382
395
|
const existingIndexes = await this.listIndexes();
|
|
383
396
|
void existingIndexes.map(async (indexName) => {
|
|
384
|
-
const info = await this.getIndexInfo(indexName);
|
|
397
|
+
const info = await this.getIndexInfo({ indexName });
|
|
385
398
|
const key = await this.getIndexCacheKey({
|
|
386
399
|
indexName,
|
|
387
400
|
metric: info.metric,
|
|
@@ -397,31 +410,42 @@ var PgVector = class extends MastraVector {
|
|
|
397
410
|
return this.mutexesByName.get(indexName);
|
|
398
411
|
}
|
|
399
412
|
getTableName(indexName) {
|
|
400
|
-
|
|
413
|
+
const parsedIndexName = parseSqlIdentifier(indexName, "index name");
|
|
414
|
+
const parsedSchemaName = this.schema ? parseSqlIdentifier(this.schema, "schema name") : void 0;
|
|
415
|
+
return parsedSchemaName ? `${parsedSchemaName}.${parsedIndexName}` : parsedIndexName;
|
|
401
416
|
}
|
|
402
417
|
transformFilter(filter) {
|
|
403
418
|
const translator = new PGFilterTranslator();
|
|
404
419
|
return translator.translate(filter);
|
|
405
420
|
}
|
|
406
|
-
async getIndexInfo(indexName) {
|
|
421
|
+
async getIndexInfo({ indexName }) {
|
|
407
422
|
if (!this.describeIndexCache.has(indexName)) {
|
|
408
|
-
this.describeIndexCache.set(indexName, await this.describeIndex(indexName));
|
|
423
|
+
this.describeIndexCache.set(indexName, await this.describeIndex({ indexName }));
|
|
409
424
|
}
|
|
410
425
|
return this.describeIndexCache.get(indexName);
|
|
411
426
|
}
|
|
412
|
-
async query(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
427
|
+
async query({
|
|
428
|
+
indexName,
|
|
429
|
+
queryVector,
|
|
430
|
+
topK = 10,
|
|
431
|
+
filter,
|
|
432
|
+
includeVector = false,
|
|
433
|
+
minScore = 0,
|
|
434
|
+
ef,
|
|
435
|
+
probes
|
|
436
|
+
}) {
|
|
437
|
+
if (!Number.isInteger(topK) || topK <= 0) {
|
|
438
|
+
throw new Error("topK must be a positive integer");
|
|
439
|
+
}
|
|
440
|
+
if (!Array.isArray(queryVector) || !queryVector.every((x) => typeof x === "number" && Number.isFinite(x))) {
|
|
441
|
+
throw new Error("queryVector must be an array of finite numbers");
|
|
442
|
+
}
|
|
419
443
|
const client = await this.pool.connect();
|
|
420
444
|
try {
|
|
421
445
|
const vectorStr = `[${queryVector.join(",")}]`;
|
|
422
446
|
const translatedFilter = this.transformFilter(filter);
|
|
423
|
-
const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter, minScore);
|
|
424
|
-
const indexInfo = await this.getIndexInfo(indexName);
|
|
447
|
+
const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter, minScore, topK);
|
|
448
|
+
const indexInfo = await this.getIndexInfo({ indexName });
|
|
425
449
|
if (indexInfo.type === "hnsw") {
|
|
426
450
|
const calculatedEf = ef ?? Math.max(topK, (indexInfo?.config?.m ?? 16) * topK);
|
|
427
451
|
const searchEf = Math.min(1e3, Math.max(1, calculatedEf));
|
|
@@ -445,7 +469,7 @@ var PgVector = class extends MastraVector {
|
|
|
445
469
|
FROM vector_scores
|
|
446
470
|
WHERE score > $1
|
|
447
471
|
ORDER BY score DESC
|
|
448
|
-
LIMIT $
|
|
472
|
+
LIMIT $2`;
|
|
449
473
|
const result = await client.query(query, filterValues);
|
|
450
474
|
return result.rows.map(({ id, score, metadata, embedding }) => ({
|
|
451
475
|
id,
|
|
@@ -457,9 +481,7 @@ var PgVector = class extends MastraVector {
|
|
|
457
481
|
client.release();
|
|
458
482
|
}
|
|
459
483
|
}
|
|
460
|
-
async upsert(
|
|
461
|
-
const params = this.normalizeArgs("upsert", args);
|
|
462
|
-
const { indexName, vectors, metadata, ids } = params;
|
|
484
|
+
async upsert({ indexName, vectors, metadata, ids }) {
|
|
463
485
|
const tableName = this.getTableName(indexName);
|
|
464
486
|
const client = await this.pool.connect();
|
|
465
487
|
try {
|
|
@@ -486,7 +508,7 @@ var PgVector = class extends MastraVector {
|
|
|
486
508
|
if (match) {
|
|
487
509
|
const [, expected, actual] = match;
|
|
488
510
|
throw new Error(
|
|
489
|
-
`Vector dimension mismatch: Index "${
|
|
511
|
+
`Vector dimension mismatch: Index "${indexName}" expects ${expected} dimensions but got ${actual} dimensions. Either use a matching embedding model or delete and recreate the index with the new dimension.`
|
|
490
512
|
);
|
|
491
513
|
}
|
|
492
514
|
}
|
|
@@ -496,8 +518,13 @@ var PgVector = class extends MastraVector {
|
|
|
496
518
|
}
|
|
497
519
|
}
|
|
498
520
|
hasher = xxhash();
|
|
499
|
-
async getIndexCacheKey(
|
|
500
|
-
|
|
521
|
+
async getIndexCacheKey({
|
|
522
|
+
indexName,
|
|
523
|
+
dimension,
|
|
524
|
+
metric,
|
|
525
|
+
type
|
|
526
|
+
}) {
|
|
527
|
+
const input = indexName + dimension + metric + (type || "ivfflat");
|
|
501
528
|
return (await this.hasher).h32(input);
|
|
502
529
|
}
|
|
503
530
|
cachedIndexExists(indexName, newKey) {
|
|
@@ -545,12 +572,13 @@ var PgVector = class extends MastraVector {
|
|
|
545
572
|
}
|
|
546
573
|
await this.setupSchemaPromise;
|
|
547
574
|
}
|
|
548
|
-
async createIndex(
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
575
|
+
async createIndex({
|
|
576
|
+
indexName,
|
|
577
|
+
dimension,
|
|
578
|
+
metric = "cosine",
|
|
579
|
+
indexConfig = {},
|
|
580
|
+
buildIndex = true
|
|
581
|
+
}) {
|
|
554
582
|
const tableName = this.getTableName(indexName);
|
|
555
583
|
if (!indexName.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
|
|
556
584
|
throw new Error("Invalid index name format");
|
|
@@ -591,20 +619,7 @@ var PgVector = class extends MastraVector {
|
|
|
591
619
|
}
|
|
592
620
|
});
|
|
593
621
|
}
|
|
594
|
-
|
|
595
|
-
* @deprecated This function is deprecated. Use buildIndex instead
|
|
596
|
-
* This function will be removed on May 20th, 2025
|
|
597
|
-
*/
|
|
598
|
-
async defineIndex(indexName, metric = "cosine", indexConfig) {
|
|
599
|
-
console.warn("defineIndex is deprecated. Use buildIndex instead. This function will be removed on May 20th, 2025");
|
|
600
|
-
return this.buildIndex({ indexName, metric, indexConfig });
|
|
601
|
-
}
|
|
602
|
-
async buildIndex(...args) {
|
|
603
|
-
const params = this.normalizeArgs("buildIndex", args, [
|
|
604
|
-
"metric",
|
|
605
|
-
"indexConfig"
|
|
606
|
-
]);
|
|
607
|
-
const { indexName, metric = "cosine", indexConfig } = params;
|
|
622
|
+
async buildIndex({ indexName, metric = "cosine", indexConfig }) {
|
|
608
623
|
const client = await this.pool.connect();
|
|
609
624
|
try {
|
|
610
625
|
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
@@ -708,7 +723,13 @@ var PgVector = class extends MastraVector {
|
|
|
708
723
|
client.release();
|
|
709
724
|
}
|
|
710
725
|
}
|
|
711
|
-
|
|
726
|
+
/**
|
|
727
|
+
* Retrieves statistics about a vector index.
|
|
728
|
+
*
|
|
729
|
+
* @param {string} indexName - The name of the index to describe
|
|
730
|
+
* @returns A promise that resolves to the index statistics including dimension, count and metric
|
|
731
|
+
*/
|
|
732
|
+
async describeIndex({ indexName }) {
|
|
712
733
|
const client = await this.pool.connect();
|
|
713
734
|
try {
|
|
714
735
|
const tableName = this.getTableName(indexName);
|
|
@@ -782,7 +803,7 @@ var PgVector = class extends MastraVector {
|
|
|
782
803
|
client.release();
|
|
783
804
|
}
|
|
784
805
|
}
|
|
785
|
-
async deleteIndex(indexName) {
|
|
806
|
+
async deleteIndex({ indexName }) {
|
|
786
807
|
const client = await this.pool.connect();
|
|
787
808
|
try {
|
|
788
809
|
const tableName = this.getTableName(indexName);
|
|
@@ -795,7 +816,7 @@ var PgVector = class extends MastraVector {
|
|
|
795
816
|
client.release();
|
|
796
817
|
}
|
|
797
818
|
}
|
|
798
|
-
async truncateIndex(indexName) {
|
|
819
|
+
async truncateIndex({ indexName }) {
|
|
799
820
|
const client = await this.pool.connect();
|
|
800
821
|
try {
|
|
801
822
|
const tableName = this.getTableName(indexName);
|
|
@@ -811,8 +832,6 @@ var PgVector = class extends MastraVector {
|
|
|
811
832
|
await this.pool.end();
|
|
812
833
|
}
|
|
813
834
|
/**
|
|
814
|
-
* @deprecated Use {@link updateVector} instead. This method will be removed on May 20th, 2025.
|
|
815
|
-
*
|
|
816
835
|
* Updates a vector by its ID with the provided vector and/or metadata.
|
|
817
836
|
* @param indexName - The name of the index containing the vector.
|
|
818
837
|
* @param id - The ID of the vector to update.
|
|
@@ -822,25 +841,7 @@ var PgVector = class extends MastraVector {
|
|
|
822
841
|
* @returns A promise that resolves when the update is complete.
|
|
823
842
|
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
824
843
|
*/
|
|
825
|
-
async
|
|
826
|
-
this.logger.warn(
|
|
827
|
-
`Deprecation Warning: updateIndexById() is deprecated.
|
|
828
|
-
Please use updateVector() instead.
|
|
829
|
-
updateIndexById() will be removed on May 20th, 2025.`
|
|
830
|
-
);
|
|
831
|
-
await this.updateVector(indexName, id, update);
|
|
832
|
-
}
|
|
833
|
-
/**
|
|
834
|
-
* Updates a vector by its ID with the provided vector and/or metadata.
|
|
835
|
-
* @param indexName - The name of the index containing the vector.
|
|
836
|
-
* @param id - The ID of the vector to update.
|
|
837
|
-
* @param update - An object containing the vector and/or metadata to update.
|
|
838
|
-
* @param update.vector - An optional array of numbers representing the new vector.
|
|
839
|
-
* @param update.metadata - An optional record containing the new metadata.
|
|
840
|
-
* @returns A promise that resolves when the update is complete.
|
|
841
|
-
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
842
|
-
*/
|
|
843
|
-
async updateVector(indexName, id, update) {
|
|
844
|
+
async updateVector({ indexName, id, update }) {
|
|
844
845
|
if (!update.vector && !update.metadata) {
|
|
845
846
|
throw new Error("No updates provided");
|
|
846
847
|
}
|
|
@@ -874,23 +875,6 @@ var PgVector = class extends MastraVector {
|
|
|
874
875
|
client.release();
|
|
875
876
|
}
|
|
876
877
|
}
|
|
877
|
-
/**
|
|
878
|
-
* @deprecated Use {@link deleteVector} instead. This method will be removed on May 20th, 2025.
|
|
879
|
-
*
|
|
880
|
-
* Deletes a vector by its ID.
|
|
881
|
-
* @param indexName - The name of the index containing the vector.
|
|
882
|
-
* @param id - The ID of the vector to delete.
|
|
883
|
-
* @returns A promise that resolves when the deletion is complete.
|
|
884
|
-
* @throws Will throw an error if the deletion operation fails.
|
|
885
|
-
*/
|
|
886
|
-
async deleteIndexById(indexName, id) {
|
|
887
|
-
this.logger.warn(
|
|
888
|
-
`Deprecation Warning: deleteIndexById() is deprecated.
|
|
889
|
-
Please use deleteVector() instead.
|
|
890
|
-
deleteIndexById() will be removed on May 20th, 2025.`
|
|
891
|
-
);
|
|
892
|
-
await this.deleteVector(indexName, id);
|
|
893
|
-
}
|
|
894
878
|
/**
|
|
895
879
|
* Deletes a vector by its ID.
|
|
896
880
|
* @param indexName - The name of the index containing the vector.
|
|
@@ -898,7 +882,7 @@ var PgVector = class extends MastraVector {
|
|
|
898
882
|
* @returns A promise that resolves when the deletion is complete.
|
|
899
883
|
* @throws Will throw an error if the deletion operation fails.
|
|
900
884
|
*/
|
|
901
|
-
async deleteVector(indexName, id) {
|
|
885
|
+
async deleteVector({ indexName, id }) {
|
|
902
886
|
const client = await this.pool.connect();
|
|
903
887
|
try {
|
|
904
888
|
const tableName = this.getTableName(indexName);
|
|
@@ -939,12 +923,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
939
923
|
}
|
|
940
924
|
super({ name: "PostgresStore" });
|
|
941
925
|
this.pgp = pgPromise();
|
|
942
|
-
|
|
943
|
-
console.warn(
|
|
944
|
-
'[DEPRECATION NOTICE] The "schema" option in PostgresStore is deprecated. Please use "schemaName" instead. Support for "schema" will be removed on May 20th, 2025.'
|
|
945
|
-
);
|
|
946
|
-
}
|
|
947
|
-
this.schema = config.schemaName ?? config.schema;
|
|
926
|
+
this.schema = config.schemaName;
|
|
948
927
|
this.db = this.pgp(
|
|
949
928
|
`connectionString` in config ? { connectionString: config.connectionString } : {
|
|
950
929
|
host: config.host,
|
|
@@ -957,7 +936,9 @@ var PostgresStore = class extends MastraStorage {
|
|
|
957
936
|
);
|
|
958
937
|
}
|
|
959
938
|
getTableName(indexName) {
|
|
960
|
-
|
|
939
|
+
const parsedIndexName = parseSqlIdentifier(indexName, "table name");
|
|
940
|
+
const parsedSchemaName = this.schema ? parseSqlIdentifier(this.schema, "schema name") : void 0;
|
|
941
|
+
return parsedSchemaName ? `${parsedSchemaName}."${parsedIndexName}"` : `"${parsedIndexName}"`;
|
|
961
942
|
}
|
|
962
943
|
async getEvalsByAgentName(agentName, type) {
|
|
963
944
|
try {
|
|
@@ -1032,12 +1013,14 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1032
1013
|
}
|
|
1033
1014
|
if (attributes) {
|
|
1034
1015
|
Object.keys(attributes).forEach((key) => {
|
|
1035
|
-
|
|
1016
|
+
const parsedKey = parseSqlIdentifier(key, "attribute key");
|
|
1017
|
+
conditions.push(`attributes->>'${parsedKey}' = $${idx++}`);
|
|
1036
1018
|
});
|
|
1037
1019
|
}
|
|
1038
1020
|
if (filters) {
|
|
1039
1021
|
Object.entries(filters).forEach(([key]) => {
|
|
1040
|
-
|
|
1022
|
+
const parsedKey = parseSqlIdentifier(key, "filter key");
|
|
1023
|
+
conditions.push(`${parsedKey} = $${idx++}`);
|
|
1041
1024
|
});
|
|
1042
1025
|
}
|
|
1043
1026
|
if (fromDate) {
|
|
@@ -1139,10 +1122,11 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1139
1122
|
}) {
|
|
1140
1123
|
try {
|
|
1141
1124
|
const columns = Object.entries(schema).map(([name, def]) => {
|
|
1125
|
+
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1142
1126
|
const constraints = [];
|
|
1143
1127
|
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
1144
1128
|
if (!def.nullable) constraints.push("NOT NULL");
|
|
1145
|
-
return `"${
|
|
1129
|
+
return `"${parsedName}" ${def.type.toUpperCase()} ${constraints.join(" ")}`;
|
|
1146
1130
|
}).join(",\n");
|
|
1147
1131
|
if (this.schema) {
|
|
1148
1132
|
await this.setupSchema();
|
|
@@ -1179,7 +1163,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1179
1163
|
}
|
|
1180
1164
|
async insert({ tableName, record }) {
|
|
1181
1165
|
try {
|
|
1182
|
-
const columns = Object.keys(record);
|
|
1166
|
+
const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
|
|
1183
1167
|
const values = Object.values(record);
|
|
1184
1168
|
const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
|
|
1185
1169
|
await this.db.none(
|
|
@@ -1193,7 +1177,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1193
1177
|
}
|
|
1194
1178
|
async load({ tableName, keys }) {
|
|
1195
1179
|
try {
|
|
1196
|
-
const keyEntries = Object.entries(keys);
|
|
1180
|
+
const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
|
|
1197
1181
|
const conditions = keyEntries.map(([key], index) => `"${key}" = $${index + 1}`).join(" AND ");
|
|
1198
1182
|
const values = keyEntries.map(([_, value]) => value);
|
|
1199
1183
|
const result = await this.db.oneOrNone(
|
package/docker-compose.perf.yaml
CHANGED
|
@@ -3,19 +3,19 @@ services:
|
|
|
3
3
|
image: pgvector/pgvector:pg16
|
|
4
4
|
container_name: 'pg-perf-test-db'
|
|
5
5
|
ports:
|
|
6
|
-
- '5435:5432'
|
|
6
|
+
- '5435:5432'
|
|
7
7
|
environment:
|
|
8
8
|
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
|
9
9
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
|
10
10
|
POSTGRES_DB: ${POSTGRES_DB:-mastra}
|
|
11
11
|
shm_size: 1gb
|
|
12
12
|
command:
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
13
|
+
- 'postgres'
|
|
14
|
+
- '-c'
|
|
15
|
+
- 'shared_buffers=512MB'
|
|
16
|
+
- '-c'
|
|
17
|
+
- 'maintenance_work_mem=1024MB'
|
|
18
|
+
- '-c'
|
|
19
|
+
- 'work_mem=512MB'
|
|
20
20
|
tmpfs:
|
|
21
|
-
- /var/lib/postgresql/data
|
|
21
|
+
- /var/lib/postgresql/data
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/pg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-alpha.1",
|
|
4
4
|
"description": "Postgres provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
"async-mutex": "^0.5.0",
|
|
24
24
|
"pg": "^8.13.3",
|
|
25
25
|
"pg-promise": "^11.11.0",
|
|
26
|
-
"xxhash-wasm": "^1.1.0"
|
|
27
|
-
"@mastra/core": "^0.9.4"
|
|
26
|
+
"xxhash-wasm": "^1.1.0"
|
|
28
27
|
},
|
|
29
28
|
"devDependencies": {
|
|
30
29
|
"@microsoft/api-extractor": "^7.52.5",
|
|
@@ -34,7 +33,11 @@
|
|
|
34
33
|
"tsup": "^8.4.0",
|
|
35
34
|
"typescript": "^5.8.2",
|
|
36
35
|
"vitest": "^3.1.2",
|
|
37
|
-
"@internal/lint": "0.0.5"
|
|
36
|
+
"@internal/lint": "0.0.5",
|
|
37
|
+
"@mastra/core": "0.10.0-alpha.1"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@mastra/core": "^0.9.4"
|
|
38
41
|
},
|
|
39
42
|
"scripts": {
|
|
40
43
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|