@mastra/pg 0.3.4-alpha.5 → 0.3.5-alpha.0
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 +38 -0
- package/dist/_tsup-dts-rollup.d.cts +19 -31
- package/dist/_tsup-dts-rollup.d.ts +19 -31
- package/dist/index.cjs +151 -90
- package/dist/index.js +151 -90
- package/docker-compose.perf.yaml +9 -9
- package/package.json +3 -3
- package/src/storage/index.ts +12 -6
- package/src/vector/index.test.ts +53 -51
- package/src/vector/index.ts +47 -21
- 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
|
}
|
|
@@ -381,7 +410,7 @@ var PgVector = class extends MastraVector {
|
|
|
381
410
|
void (async () => {
|
|
382
411
|
const existingIndexes = await this.listIndexes();
|
|
383
412
|
void existingIndexes.map(async (indexName) => {
|
|
384
|
-
const info = await this.getIndexInfo(indexName);
|
|
413
|
+
const info = await this.getIndexInfo({ indexName });
|
|
385
414
|
const key = await this.getIndexCacheKey({
|
|
386
415
|
indexName,
|
|
387
416
|
metric: info.metric,
|
|
@@ -397,15 +426,19 @@ var PgVector = class extends MastraVector {
|
|
|
397
426
|
return this.mutexesByName.get(indexName);
|
|
398
427
|
}
|
|
399
428
|
getTableName(indexName) {
|
|
400
|
-
|
|
429
|
+
const parsedIndexName = parseSqlIdentifier(indexName, "index name");
|
|
430
|
+
const parsedSchemaName = this.schema ? parseSqlIdentifier(this.schema, "schema name") : void 0;
|
|
431
|
+
return parsedSchemaName ? `${parsedSchemaName}.${parsedIndexName}` : parsedIndexName;
|
|
401
432
|
}
|
|
402
433
|
transformFilter(filter) {
|
|
403
434
|
const translator = new PGFilterTranslator();
|
|
404
435
|
return translator.translate(filter);
|
|
405
436
|
}
|
|
406
|
-
async getIndexInfo(
|
|
437
|
+
async getIndexInfo(...args) {
|
|
438
|
+
const params = this.normalizeArgs("getIndexInfo", args);
|
|
439
|
+
const { indexName } = params;
|
|
407
440
|
if (!this.describeIndexCache.has(indexName)) {
|
|
408
|
-
this.describeIndexCache.set(indexName, await this.describeIndex(indexName));
|
|
441
|
+
this.describeIndexCache.set(indexName, await this.describeIndex({ indexName }));
|
|
409
442
|
}
|
|
410
443
|
return this.describeIndexCache.get(indexName);
|
|
411
444
|
}
|
|
@@ -416,12 +449,18 @@ var PgVector = class extends MastraVector {
|
|
|
416
449
|
"probes"
|
|
417
450
|
]);
|
|
418
451
|
const { indexName, queryVector, topK = 10, filter, includeVector = false, minScore = 0, ef, probes } = params;
|
|
452
|
+
if (!Number.isInteger(topK) || topK <= 0) {
|
|
453
|
+
throw new Error("topK must be a positive integer");
|
|
454
|
+
}
|
|
455
|
+
if (!Array.isArray(queryVector) || !queryVector.every((x) => typeof x === "number" && Number.isFinite(x))) {
|
|
456
|
+
throw new Error("queryVector must be an array of finite numbers");
|
|
457
|
+
}
|
|
419
458
|
const client = await this.pool.connect();
|
|
420
459
|
try {
|
|
421
460
|
const vectorStr = `[${queryVector.join(",")}]`;
|
|
422
461
|
const translatedFilter = this.transformFilter(filter);
|
|
423
|
-
const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter, minScore);
|
|
424
|
-
const indexInfo = await this.getIndexInfo(indexName);
|
|
462
|
+
const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter, minScore, topK);
|
|
463
|
+
const indexInfo = await this.getIndexInfo({ indexName });
|
|
425
464
|
if (indexInfo.type === "hnsw") {
|
|
426
465
|
const calculatedEf = ef ?? Math.max(topK, (indexInfo?.config?.m ?? 16) * topK);
|
|
427
466
|
const searchEf = Math.min(1e3, Math.max(1, calculatedEf));
|
|
@@ -445,7 +484,7 @@ var PgVector = class extends MastraVector {
|
|
|
445
484
|
FROM vector_scores
|
|
446
485
|
WHERE score > $1
|
|
447
486
|
ORDER BY score DESC
|
|
448
|
-
LIMIT $
|
|
487
|
+
LIMIT $2`;
|
|
449
488
|
const result = await client.query(query, filterValues);
|
|
450
489
|
return result.rows.map(({ id, score, metadata, embedding }) => ({
|
|
451
490
|
id,
|
|
@@ -708,7 +747,16 @@ var PgVector = class extends MastraVector {
|
|
|
708
747
|
client.release();
|
|
709
748
|
}
|
|
710
749
|
}
|
|
711
|
-
|
|
750
|
+
/**
|
|
751
|
+
* Retrieves statistics about a vector index.
|
|
752
|
+
*
|
|
753
|
+
* @param params - The parameters for describing an index
|
|
754
|
+
* @param params.indexName - The name of the index to describe
|
|
755
|
+
* @returns A promise that resolves to the index statistics including dimension, count and metric
|
|
756
|
+
*/
|
|
757
|
+
async describeIndex(...args) {
|
|
758
|
+
const params = this.normalizeArgs("describeIndex", args);
|
|
759
|
+
const { indexName } = params;
|
|
712
760
|
const client = await this.pool.connect();
|
|
713
761
|
try {
|
|
714
762
|
const tableName = this.getTableName(indexName);
|
|
@@ -782,7 +830,9 @@ var PgVector = class extends MastraVector {
|
|
|
782
830
|
client.release();
|
|
783
831
|
}
|
|
784
832
|
}
|
|
785
|
-
async deleteIndex(
|
|
833
|
+
async deleteIndex(...args) {
|
|
834
|
+
const params = this.normalizeArgs("deleteIndex", args);
|
|
835
|
+
const { indexName } = params;
|
|
786
836
|
const client = await this.pool.connect();
|
|
787
837
|
try {
|
|
788
838
|
const tableName = this.getTableName(indexName);
|
|
@@ -795,7 +845,9 @@ var PgVector = class extends MastraVector {
|
|
|
795
845
|
client.release();
|
|
796
846
|
}
|
|
797
847
|
}
|
|
798
|
-
async truncateIndex(
|
|
848
|
+
async truncateIndex(...args) {
|
|
849
|
+
const params = this.normalizeArgs("truncateIndex", args);
|
|
850
|
+
const { indexName } = params;
|
|
799
851
|
const client = await this.pool.connect();
|
|
800
852
|
try {
|
|
801
853
|
const tableName = this.getTableName(indexName);
|
|
@@ -828,7 +880,7 @@ var PgVector = class extends MastraVector {
|
|
|
828
880
|
Please use updateVector() instead.
|
|
829
881
|
updateIndexById() will be removed on May 20th, 2025.`
|
|
830
882
|
);
|
|
831
|
-
await this.updateVector(indexName, id, update);
|
|
883
|
+
await this.updateVector({ indexName, id, update });
|
|
832
884
|
}
|
|
833
885
|
/**
|
|
834
886
|
* Updates a vector by its ID with the provided vector and/or metadata.
|
|
@@ -840,7 +892,9 @@ var PgVector = class extends MastraVector {
|
|
|
840
892
|
* @returns A promise that resolves when the update is complete.
|
|
841
893
|
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
842
894
|
*/
|
|
843
|
-
async updateVector(
|
|
895
|
+
async updateVector(...args) {
|
|
896
|
+
const params = this.normalizeArgs("updateVector", args);
|
|
897
|
+
const { indexName, id, update } = params;
|
|
844
898
|
if (!update.vector && !update.metadata) {
|
|
845
899
|
throw new Error("No updates provided");
|
|
846
900
|
}
|
|
@@ -889,7 +943,7 @@ var PgVector = class extends MastraVector {
|
|
|
889
943
|
Please use deleteVector() instead.
|
|
890
944
|
deleteIndexById() will be removed on May 20th, 2025.`
|
|
891
945
|
);
|
|
892
|
-
await this.deleteVector(indexName, id);
|
|
946
|
+
await this.deleteVector({ indexName, id });
|
|
893
947
|
}
|
|
894
948
|
/**
|
|
895
949
|
* Deletes a vector by its ID.
|
|
@@ -898,7 +952,9 @@ var PgVector = class extends MastraVector {
|
|
|
898
952
|
* @returns A promise that resolves when the deletion is complete.
|
|
899
953
|
* @throws Will throw an error if the deletion operation fails.
|
|
900
954
|
*/
|
|
901
|
-
async deleteVector(
|
|
955
|
+
async deleteVector(...args) {
|
|
956
|
+
const params = this.normalizeArgs("deleteVector", args);
|
|
957
|
+
const { indexName, id } = params;
|
|
902
958
|
const client = await this.pool.connect();
|
|
903
959
|
try {
|
|
904
960
|
const tableName = this.getTableName(indexName);
|
|
@@ -957,7 +1013,9 @@ var PostgresStore = class extends MastraStorage {
|
|
|
957
1013
|
);
|
|
958
1014
|
}
|
|
959
1015
|
getTableName(indexName) {
|
|
960
|
-
|
|
1016
|
+
const parsedIndexName = parseSqlIdentifier(indexName, "table name");
|
|
1017
|
+
const parsedSchemaName = this.schema ? parseSqlIdentifier(this.schema, "schema name") : void 0;
|
|
1018
|
+
return parsedSchemaName ? `${parsedSchemaName}."${parsedIndexName}"` : `"${parsedIndexName}"`;
|
|
961
1019
|
}
|
|
962
1020
|
async getEvalsByAgentName(agentName, type) {
|
|
963
1021
|
try {
|
|
@@ -1032,12 +1090,14 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1032
1090
|
}
|
|
1033
1091
|
if (attributes) {
|
|
1034
1092
|
Object.keys(attributes).forEach((key) => {
|
|
1035
|
-
|
|
1093
|
+
const parsedKey = parseSqlIdentifier(key, "attribute key");
|
|
1094
|
+
conditions.push(`attributes->>'${parsedKey}' = $${idx++}`);
|
|
1036
1095
|
});
|
|
1037
1096
|
}
|
|
1038
1097
|
if (filters) {
|
|
1039
1098
|
Object.entries(filters).forEach(([key]) => {
|
|
1040
|
-
|
|
1099
|
+
const parsedKey = parseSqlIdentifier(key, "filter key");
|
|
1100
|
+
conditions.push(`${parsedKey} = $${idx++}`);
|
|
1041
1101
|
});
|
|
1042
1102
|
}
|
|
1043
1103
|
if (fromDate) {
|
|
@@ -1139,10 +1199,11 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1139
1199
|
}) {
|
|
1140
1200
|
try {
|
|
1141
1201
|
const columns = Object.entries(schema).map(([name, def]) => {
|
|
1202
|
+
const parsedName = parseSqlIdentifier(name, "column name");
|
|
1142
1203
|
const constraints = [];
|
|
1143
1204
|
if (def.primaryKey) constraints.push("PRIMARY KEY");
|
|
1144
1205
|
if (!def.nullable) constraints.push("NOT NULL");
|
|
1145
|
-
return `"${
|
|
1206
|
+
return `"${parsedName}" ${def.type.toUpperCase()} ${constraints.join(" ")}`;
|
|
1146
1207
|
}).join(",\n");
|
|
1147
1208
|
if (this.schema) {
|
|
1148
1209
|
await this.setupSchema();
|
|
@@ -1179,7 +1240,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1179
1240
|
}
|
|
1180
1241
|
async insert({ tableName, record }) {
|
|
1181
1242
|
try {
|
|
1182
|
-
const columns = Object.keys(record);
|
|
1243
|
+
const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
|
|
1183
1244
|
const values = Object.values(record);
|
|
1184
1245
|
const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
|
|
1185
1246
|
await this.db.none(
|
|
@@ -1193,7 +1254,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1193
1254
|
}
|
|
1194
1255
|
async load({ tableName, keys }) {
|
|
1195
1256
|
try {
|
|
1196
|
-
const keyEntries = Object.entries(keys);
|
|
1257
|
+
const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
|
|
1197
1258
|
const conditions = keyEntries.map(([key], index) => `"${key}" = $${index + 1}`).join(" AND ");
|
|
1198
1259
|
const values = keyEntries.map(([_, value]) => value);
|
|
1199
1260
|
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.
|
|
3
|
+
"version": "0.3.5-alpha.0",
|
|
4
4
|
"description": "Postgres provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"pg": "^8.13.3",
|
|
25
25
|
"pg-promise": "^11.11.0",
|
|
26
26
|
"xxhash-wasm": "^1.1.0",
|
|
27
|
-
"@mastra/core": "^0.9.
|
|
27
|
+
"@mastra/core": "^0.9.5-alpha.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@microsoft/api-extractor": "^7.52.5",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"tsup": "^8.4.0",
|
|
35
35
|
"typescript": "^5.8.2",
|
|
36
36
|
"vitest": "^3.1.2",
|
|
37
|
-
"@internal/lint": "0.0.
|
|
37
|
+
"@internal/lint": "0.0.5"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|
package/src/storage/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
WorkflowRun,
|
|
17
17
|
WorkflowRuns,
|
|
18
18
|
} from '@mastra/core/storage';
|
|
19
|
+
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
19
20
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
20
21
|
import pgPromise from 'pg-promise';
|
|
21
22
|
import type { ISSLConfig } from 'pg-promise/typescript/pg-subset';
|
|
@@ -93,7 +94,9 @@ export class PostgresStore extends MastraStorage {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
private getTableName(indexName: string) {
|
|
96
|
-
|
|
97
|
+
const parsedIndexName = parseSqlIdentifier(indexName, 'table name');
|
|
98
|
+
const parsedSchemaName = this.schema ? parseSqlIdentifier(this.schema, 'schema name') : undefined;
|
|
99
|
+
return parsedSchemaName ? `${parsedSchemaName}."${parsedIndexName}"` : `"${parsedIndexName}"`;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
|
|
@@ -192,13 +195,15 @@ export class PostgresStore extends MastraStorage {
|
|
|
192
195
|
}
|
|
193
196
|
if (attributes) {
|
|
194
197
|
Object.keys(attributes).forEach(key => {
|
|
195
|
-
|
|
198
|
+
const parsedKey = parseSqlIdentifier(key, 'attribute key');
|
|
199
|
+
conditions.push(`attributes->>'${parsedKey}' = \$${idx++}`);
|
|
196
200
|
});
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
if (filters) {
|
|
200
204
|
Object.entries(filters).forEach(([key]) => {
|
|
201
|
-
|
|
205
|
+
const parsedKey = parseSqlIdentifier(key, 'filter key');
|
|
206
|
+
conditions.push(`${parsedKey} = \$${idx++}`);
|
|
202
207
|
});
|
|
203
208
|
}
|
|
204
209
|
|
|
@@ -341,10 +346,11 @@ export class PostgresStore extends MastraStorage {
|
|
|
341
346
|
try {
|
|
342
347
|
const columns = Object.entries(schema)
|
|
343
348
|
.map(([name, def]) => {
|
|
349
|
+
const parsedName = parseSqlIdentifier(name, 'column name');
|
|
344
350
|
const constraints = [];
|
|
345
351
|
if (def.primaryKey) constraints.push('PRIMARY KEY');
|
|
346
352
|
if (!def.nullable) constraints.push('NOT NULL');
|
|
347
|
-
return `"${
|
|
353
|
+
return `"${parsedName}" ${def.type.toUpperCase()} ${constraints.join(' ')}`;
|
|
348
354
|
})
|
|
349
355
|
.join(',\n');
|
|
350
356
|
|
|
@@ -392,7 +398,7 @@ export class PostgresStore extends MastraStorage {
|
|
|
392
398
|
|
|
393
399
|
async insert({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): Promise<void> {
|
|
394
400
|
try {
|
|
395
|
-
const columns = Object.keys(record);
|
|
401
|
+
const columns = Object.keys(record).map(col => parseSqlIdentifier(col, 'column name'));
|
|
396
402
|
const values = Object.values(record);
|
|
397
403
|
const placeholders = values.map((_, i) => `$${i + 1}`).join(', ');
|
|
398
404
|
|
|
@@ -408,7 +414,7 @@ export class PostgresStore extends MastraStorage {
|
|
|
408
414
|
|
|
409
415
|
async load<R>({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, string> }): Promise<R | null> {
|
|
410
416
|
try {
|
|
411
|
-
const keyEntries = Object.entries(keys);
|
|
417
|
+
const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, 'column name'), value]);
|
|
412
418
|
const conditions = keyEntries.map(([key], index) => `"${key}" = $${index + 1}`).join(' AND ');
|
|
413
419
|
const values = keyEntries.map(([_, value]) => value);
|
|
414
420
|
|