@mastra/pg 1.0.0-beta.1 → 1.0.0-beta.2
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 +9 -0
- package/README.md +3 -0
- package/dist/index.cjs +286 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +286 -14
- package/dist/index.js.map +1 -1
- package/dist/vector/index.d.ts +11 -3
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/sql-builder.d.ts +4 -0
- package/dist/vector/sql-builder.d.ts.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -126,12 +126,20 @@ var createBasicOperator = (symbol) => {
|
|
|
126
126
|
};
|
|
127
127
|
};
|
|
128
128
|
var createNumericOperator = (symbol) => {
|
|
129
|
-
return (key, paramIndex) => {
|
|
129
|
+
return (key, paramIndex, value) => {
|
|
130
130
|
const jsonPathKey = parseJsonPathKey(key);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
const isNumeric = typeof value === "number" || typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "";
|
|
132
|
+
if (isNumeric) {
|
|
133
|
+
return {
|
|
134
|
+
sql: `(metadata#>>'{${jsonPathKey}}')::numeric ${symbol} $${paramIndex}::numeric`,
|
|
135
|
+
needsValue: true
|
|
136
|
+
};
|
|
137
|
+
} else {
|
|
138
|
+
return {
|
|
139
|
+
sql: `metadata#>>'{${jsonPathKey}}' ${symbol} $${paramIndex}::text`,
|
|
140
|
+
needsValue: true
|
|
141
|
+
};
|
|
142
|
+
}
|
|
135
143
|
};
|
|
136
144
|
};
|
|
137
145
|
function buildElemMatchConditions(value, paramIndex) {
|
|
@@ -312,6 +320,83 @@ var parseJsonPathKey = (key) => {
|
|
|
312
320
|
function escapeLikePattern(str) {
|
|
313
321
|
return str.replace(/([%_\\])/g, "\\$1");
|
|
314
322
|
}
|
|
323
|
+
function buildDeleteFilterQuery(filter) {
|
|
324
|
+
const values = [];
|
|
325
|
+
function buildCondition(key, value, parentPath) {
|
|
326
|
+
if (["$and", "$or", "$not", "$nor"].includes(key)) {
|
|
327
|
+
return handleLogicalOperator(key, value);
|
|
328
|
+
}
|
|
329
|
+
if (!value || typeof value !== "object") {
|
|
330
|
+
values.push(value);
|
|
331
|
+
return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
|
|
332
|
+
}
|
|
333
|
+
const [[operator, operatorValue] = []] = Object.entries(value);
|
|
334
|
+
if (operator === "$not") {
|
|
335
|
+
const entries = Object.entries(operatorValue);
|
|
336
|
+
const conditions2 = entries.map(([nestedOp, nestedValue]) => {
|
|
337
|
+
if (!FILTER_OPERATORS[nestedOp]) {
|
|
338
|
+
throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
|
|
339
|
+
}
|
|
340
|
+
const operatorFn2 = FILTER_OPERATORS[nestedOp];
|
|
341
|
+
const operatorResult2 = operatorFn2(key, values.length + 1, nestedValue);
|
|
342
|
+
if (operatorResult2.needsValue) {
|
|
343
|
+
values.push(nestedValue);
|
|
344
|
+
}
|
|
345
|
+
return operatorResult2.sql;
|
|
346
|
+
}).join(" AND ");
|
|
347
|
+
return `NOT (${conditions2})`;
|
|
348
|
+
}
|
|
349
|
+
const operatorFn = FILTER_OPERATORS[operator];
|
|
350
|
+
const operatorResult = operatorFn(key, values.length + 1, operatorValue);
|
|
351
|
+
if (operatorResult.needsValue) {
|
|
352
|
+
const transformedValue = operatorResult.transformValue ? operatorResult.transformValue() : operatorValue;
|
|
353
|
+
if (Array.isArray(transformedValue) && operator === "$elemMatch") {
|
|
354
|
+
values.push(...transformedValue);
|
|
355
|
+
} else {
|
|
356
|
+
values.push(transformedValue);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return operatorResult.sql;
|
|
360
|
+
}
|
|
361
|
+
function handleLogicalOperator(key, value, parentPath) {
|
|
362
|
+
if (key === "$not") {
|
|
363
|
+
const entries = Object.entries(value);
|
|
364
|
+
const conditions3 = entries.map(([fieldKey, fieldValue]) => buildCondition(fieldKey, fieldValue)).join(" AND ");
|
|
365
|
+
return `NOT (${conditions3})`;
|
|
366
|
+
}
|
|
367
|
+
if (!value || value.length === 0) {
|
|
368
|
+
switch (key) {
|
|
369
|
+
case "$and":
|
|
370
|
+
case "$nor":
|
|
371
|
+
return "true";
|
|
372
|
+
// Empty $and/$nor match everything
|
|
373
|
+
case "$or":
|
|
374
|
+
return "false";
|
|
375
|
+
// Empty $or matches nothing
|
|
376
|
+
default:
|
|
377
|
+
return "true";
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const joinOperator = key === "$or" || key === "$nor" ? "OR" : "AND";
|
|
381
|
+
const conditions2 = value.map((f) => {
|
|
382
|
+
const entries = Object.entries(f || {});
|
|
383
|
+
if (entries.length === 0) return "";
|
|
384
|
+
const [firstKey, firstValue] = entries[0] || [];
|
|
385
|
+
if (["$and", "$or", "$not", "$nor"].includes(firstKey)) {
|
|
386
|
+
return buildCondition(firstKey, firstValue);
|
|
387
|
+
}
|
|
388
|
+
return entries.map(([k, v]) => buildCondition(k, v)).join(` ${joinOperator} `);
|
|
389
|
+
});
|
|
390
|
+
const joined = conditions2.join(` ${joinOperator} `);
|
|
391
|
+
const operatorFn = FILTER_OPERATORS[key];
|
|
392
|
+
return operatorFn(joined, 0, value).sql;
|
|
393
|
+
}
|
|
394
|
+
if (!filter) {
|
|
395
|
+
return { sql: "", values };
|
|
396
|
+
}
|
|
397
|
+
const conditions = Object.entries(filter).map(([key, value]) => buildCondition(key, value)).filter(Boolean).join(" AND ");
|
|
398
|
+
return { sql: conditions ? `WHERE ${conditions}` : "", values };
|
|
399
|
+
}
|
|
315
400
|
function buildFilterQuery(filter, minScore, topK) {
|
|
316
401
|
const values = [minScore, topK];
|
|
317
402
|
function buildCondition(key, value, parentPath) {
|
|
@@ -633,11 +718,31 @@ var PgVector = class extends MastraVector {
|
|
|
633
718
|
client.release();
|
|
634
719
|
}
|
|
635
720
|
}
|
|
636
|
-
async upsert({
|
|
721
|
+
async upsert({
|
|
722
|
+
indexName,
|
|
723
|
+
vectors,
|
|
724
|
+
metadata,
|
|
725
|
+
ids,
|
|
726
|
+
deleteFilter
|
|
727
|
+
}) {
|
|
637
728
|
const { tableName } = this.getTableName(indexName);
|
|
638
729
|
const client = await this.pool.connect();
|
|
639
730
|
try {
|
|
640
731
|
await client.query("BEGIN");
|
|
732
|
+
if (deleteFilter) {
|
|
733
|
+
this.logger?.debug(`Deleting vectors matching filter before upsert`, { indexName, deleteFilter });
|
|
734
|
+
const translatedFilter = this.transformFilter(deleteFilter);
|
|
735
|
+
const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(translatedFilter);
|
|
736
|
+
const whereClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
|
|
737
|
+
if (whereClause) {
|
|
738
|
+
const deleteQuery = `DELETE FROM ${tableName} WHERE ${whereClause}`;
|
|
739
|
+
const result = await client.query(deleteQuery, filterValues);
|
|
740
|
+
this.logger?.debug(`Deleted ${result.rowCount || 0} vectors before upsert`, {
|
|
741
|
+
indexName,
|
|
742
|
+
deletedCount: result.rowCount || 0
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
641
746
|
const vectorIds = ids || vectors.map(() => crypto.randomUUID());
|
|
642
747
|
const vectorType = this.getVectorTypeName();
|
|
643
748
|
for (let i = 0; i < vectors.length; i++) {
|
|
@@ -653,6 +758,11 @@ var PgVector = class extends MastraVector {
|
|
|
653
758
|
await client.query(query, [vectorIds[i], `[${vectors[i]?.join(",")}]`, JSON.stringify(metadata?.[i] || {})]);
|
|
654
759
|
}
|
|
655
760
|
await client.query("COMMIT");
|
|
761
|
+
this.logger?.debug(`Upserted ${vectors.length} vectors to ${indexName}`, {
|
|
762
|
+
indexName,
|
|
763
|
+
vectorCount: vectors.length,
|
|
764
|
+
hadDeleteFilter: !!deleteFilter
|
|
765
|
+
});
|
|
656
766
|
return vectorIds;
|
|
657
767
|
} catch (error) {
|
|
658
768
|
await client.query("ROLLBACK");
|
|
@@ -1210,17 +1320,36 @@ var PgVector = class extends MastraVector {
|
|
|
1210
1320
|
* @returns A promise that resolves when the update is complete.
|
|
1211
1321
|
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
1212
1322
|
*/
|
|
1213
|
-
async updateVector({ indexName, id, update }) {
|
|
1323
|
+
async updateVector({ indexName, id, filter, update }) {
|
|
1214
1324
|
let client;
|
|
1215
1325
|
try {
|
|
1216
1326
|
if (!update.vector && !update.metadata) {
|
|
1217
1327
|
throw new Error("No updates provided");
|
|
1218
1328
|
}
|
|
1329
|
+
if (!id && !filter) {
|
|
1330
|
+
throw new MastraError({
|
|
1331
|
+
id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_MISSING_PARAMS",
|
|
1332
|
+
text: "Either id or filter must be provided",
|
|
1333
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1334
|
+
category: ErrorCategory.USER,
|
|
1335
|
+
details: { indexName }
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
if (id && filter) {
|
|
1339
|
+
throw new MastraError({
|
|
1340
|
+
id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_CONFLICTING_PARAMS",
|
|
1341
|
+
text: "Cannot provide both id and filter - they are mutually exclusive",
|
|
1342
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1343
|
+
category: ErrorCategory.USER,
|
|
1344
|
+
details: { indexName }
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1219
1347
|
client = await this.pool.connect();
|
|
1220
|
-
|
|
1221
|
-
let values = [id];
|
|
1222
|
-
let valueIndex = 2;
|
|
1348
|
+
const { tableName } = this.getTableName(indexName);
|
|
1223
1349
|
const vectorType = this.getVectorTypeName();
|
|
1350
|
+
let updateParts = [];
|
|
1351
|
+
let values = [];
|
|
1352
|
+
let valueIndex = 1;
|
|
1224
1353
|
if (update.vector) {
|
|
1225
1354
|
updateParts.push(`embedding = $${valueIndex}::${vectorType}`);
|
|
1226
1355
|
values.push(`[${update.vector.join(",")}]`);
|
|
@@ -1229,18 +1358,60 @@ var PgVector = class extends MastraVector {
|
|
|
1229
1358
|
if (update.metadata) {
|
|
1230
1359
|
updateParts.push(`metadata = $${valueIndex}::jsonb`);
|
|
1231
1360
|
values.push(JSON.stringify(update.metadata));
|
|
1361
|
+
valueIndex++;
|
|
1232
1362
|
}
|
|
1233
1363
|
if (updateParts.length === 0) {
|
|
1234
1364
|
return;
|
|
1235
1365
|
}
|
|
1236
|
-
|
|
1366
|
+
let whereClause;
|
|
1367
|
+
let whereValues;
|
|
1368
|
+
if (id) {
|
|
1369
|
+
whereClause = `vector_id = $${valueIndex}`;
|
|
1370
|
+
whereValues = [id];
|
|
1371
|
+
} else {
|
|
1372
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
1373
|
+
throw new MastraError({
|
|
1374
|
+
id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_EMPTY_FILTER",
|
|
1375
|
+
text: "Cannot update with empty filter",
|
|
1376
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1377
|
+
category: ErrorCategory.USER,
|
|
1378
|
+
details: { indexName }
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
const translatedFilter = this.transformFilter(filter);
|
|
1382
|
+
const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(translatedFilter);
|
|
1383
|
+
whereClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
|
|
1384
|
+
if (!whereClause) {
|
|
1385
|
+
throw new MastraError({
|
|
1386
|
+
id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_INVALID_FILTER",
|
|
1387
|
+
text: "Filter produced empty WHERE clause",
|
|
1388
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1389
|
+
category: ErrorCategory.USER,
|
|
1390
|
+
details: { indexName, filter: JSON.stringify(filter) }
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
whereClause = whereClause.replace(/\$(\d+)/g, (match, num) => {
|
|
1394
|
+
const newIndex = parseInt(num) + valueIndex - 1;
|
|
1395
|
+
return `$${newIndex}`;
|
|
1396
|
+
});
|
|
1397
|
+
whereValues = filterValues;
|
|
1398
|
+
}
|
|
1237
1399
|
const query = `
|
|
1238
1400
|
UPDATE ${tableName}
|
|
1239
1401
|
SET ${updateParts.join(", ")}
|
|
1240
|
-
WHERE
|
|
1402
|
+
WHERE ${whereClause}
|
|
1241
1403
|
`;
|
|
1242
|
-
await client.query(query, values);
|
|
1404
|
+
const result = await client.query(query, [...values, ...whereValues]);
|
|
1405
|
+
this.logger?.info(`Updated ${result.rowCount || 0} vectors in ${indexName}`, {
|
|
1406
|
+
indexName,
|
|
1407
|
+
id: id ? id : void 0,
|
|
1408
|
+
filter: filter ? filter : void 0,
|
|
1409
|
+
updatedCount: result.rowCount || 0
|
|
1410
|
+
});
|
|
1243
1411
|
} catch (error) {
|
|
1412
|
+
if (error instanceof MastraError) {
|
|
1413
|
+
throw error;
|
|
1414
|
+
}
|
|
1244
1415
|
const mastraError = new MastraError(
|
|
1245
1416
|
{
|
|
1246
1417
|
id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_VECTOR_FAILED",
|
|
@@ -1248,7 +1419,8 @@ var PgVector = class extends MastraVector {
|
|
|
1248
1419
|
category: ErrorCategory.THIRD_PARTY,
|
|
1249
1420
|
details: {
|
|
1250
1421
|
indexName,
|
|
1251
|
-
id
|
|
1422
|
+
...id && { id },
|
|
1423
|
+
...filter && { filter: JSON.stringify(filter) }
|
|
1252
1424
|
}
|
|
1253
1425
|
},
|
|
1254
1426
|
error
|
|
@@ -1295,6 +1467,106 @@ var PgVector = class extends MastraVector {
|
|
|
1295
1467
|
client?.release();
|
|
1296
1468
|
}
|
|
1297
1469
|
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Delete vectors matching a metadata filter.
|
|
1472
|
+
* @param indexName - The name of the index containing the vectors.
|
|
1473
|
+
* @param filter - The filter to match vectors for deletion.
|
|
1474
|
+
* @returns A promise that resolves when the deletion is complete.
|
|
1475
|
+
* @throws Will throw an error if the deletion operation fails.
|
|
1476
|
+
*/
|
|
1477
|
+
async deleteVectors({ indexName, filter, ids }) {
|
|
1478
|
+
let client;
|
|
1479
|
+
try {
|
|
1480
|
+
client = await this.pool.connect();
|
|
1481
|
+
const { tableName } = this.getTableName(indexName);
|
|
1482
|
+
if (!filter && !ids) {
|
|
1483
|
+
throw new MastraError({
|
|
1484
|
+
id: "MASTRA_STORAGE_PG_VECTOR_DELETE_MISSING_PARAMS",
|
|
1485
|
+
text: "Either filter or ids must be provided",
|
|
1486
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1487
|
+
category: ErrorCategory.USER,
|
|
1488
|
+
details: { indexName }
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
if (filter && ids) {
|
|
1492
|
+
throw new MastraError({
|
|
1493
|
+
id: "MASTRA_STORAGE_PG_VECTOR_DELETE_CONFLICTING_PARAMS",
|
|
1494
|
+
text: "Cannot provide both filter and ids - they are mutually exclusive",
|
|
1495
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1496
|
+
category: ErrorCategory.USER,
|
|
1497
|
+
details: { indexName }
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
let query;
|
|
1501
|
+
let values;
|
|
1502
|
+
if (ids) {
|
|
1503
|
+
if (ids.length === 0) {
|
|
1504
|
+
throw new MastraError({
|
|
1505
|
+
id: "MASTRA_STORAGE_PG_VECTOR_DELETE_EMPTY_IDS",
|
|
1506
|
+
text: "Cannot delete with empty ids array",
|
|
1507
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1508
|
+
category: ErrorCategory.USER,
|
|
1509
|
+
details: { indexName }
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
const placeholders = ids.map((_, i) => `$${i + 1}`).join(", ");
|
|
1513
|
+
query = `DELETE FROM ${tableName} WHERE vector_id IN (${placeholders})`;
|
|
1514
|
+
values = ids;
|
|
1515
|
+
} else {
|
|
1516
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
1517
|
+
throw new MastraError({
|
|
1518
|
+
id: "MASTRA_STORAGE_PG_VECTOR_DELETE_EMPTY_FILTER",
|
|
1519
|
+
text: "Cannot delete with empty filter. Use deleteIndex to delete all vectors.",
|
|
1520
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1521
|
+
category: ErrorCategory.USER,
|
|
1522
|
+
details: { indexName }
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
const translatedFilter = this.transformFilter(filter);
|
|
1526
|
+
const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(translatedFilter);
|
|
1527
|
+
const whereClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
|
|
1528
|
+
if (!whereClause) {
|
|
1529
|
+
throw new MastraError({
|
|
1530
|
+
id: "MASTRA_STORAGE_PG_VECTOR_DELETE_INVALID_FILTER",
|
|
1531
|
+
text: "Filter produced empty WHERE clause",
|
|
1532
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1533
|
+
category: ErrorCategory.USER,
|
|
1534
|
+
details: { indexName, filter: JSON.stringify(filter) }
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
query = `DELETE FROM ${tableName} WHERE ${whereClause}`;
|
|
1538
|
+
values = filterValues;
|
|
1539
|
+
}
|
|
1540
|
+
const result = await client.query(query, values);
|
|
1541
|
+
this.logger?.info(`Deleted ${result.rowCount || 0} vectors from ${indexName}`, {
|
|
1542
|
+
indexName,
|
|
1543
|
+
filter: filter ? filter : void 0,
|
|
1544
|
+
ids: ids ? ids : void 0,
|
|
1545
|
+
deletedCount: result.rowCount || 0
|
|
1546
|
+
});
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
if (error instanceof MastraError) {
|
|
1549
|
+
throw error;
|
|
1550
|
+
}
|
|
1551
|
+
const mastraError = new MastraError(
|
|
1552
|
+
{
|
|
1553
|
+
id: "MASTRA_STORAGE_PG_VECTOR_DELETE_VECTORS_FAILED",
|
|
1554
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1555
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1556
|
+
details: {
|
|
1557
|
+
indexName,
|
|
1558
|
+
...filter && { filter: JSON.stringify(filter) },
|
|
1559
|
+
...ids && { idsCount: ids.length }
|
|
1560
|
+
}
|
|
1561
|
+
},
|
|
1562
|
+
error
|
|
1563
|
+
);
|
|
1564
|
+
this.logger?.trackException(mastraError);
|
|
1565
|
+
throw mastraError;
|
|
1566
|
+
} finally {
|
|
1567
|
+
client?.release();
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1298
1570
|
};
|
|
1299
1571
|
function getSchemaName(schema) {
|
|
1300
1572
|
return schema ? `"${parseSqlIdentifier(schema, "schema name")}"` : void 0;
|