@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
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import { parseFieldKey } from '@mastra/core/utils';
|
|
2
|
-
import type {
|
|
3
|
-
BasicOperator,
|
|
4
|
-
NumericOperator,
|
|
5
|
-
ArrayOperator,
|
|
6
|
-
ElementOperator,
|
|
7
|
-
LogicalOperator,
|
|
8
|
-
RegexOperator,
|
|
9
|
-
VectorFilter,
|
|
10
|
-
} from '@mastra/core/vector/filter';
|
|
11
|
-
import type { PGVectorFilter } from './filter';
|
|
12
|
-
|
|
13
|
-
type OperatorType =
|
|
14
|
-
| BasicOperator
|
|
15
|
-
| NumericOperator
|
|
16
|
-
| ArrayOperator
|
|
17
|
-
| ElementOperator
|
|
18
|
-
| LogicalOperator
|
|
19
|
-
| '$contains'
|
|
20
|
-
| Exclude<RegexOperator, '$options'>
|
|
21
|
-
| '$size';
|
|
22
|
-
|
|
23
|
-
type FilterOperator = {
|
|
24
|
-
sql: string;
|
|
25
|
-
needsValue: boolean;
|
|
26
|
-
transformValue?: () => any;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type OperatorFn = (key: string, paramIndex: number, value?: any) => FilterOperator;
|
|
30
|
-
|
|
31
|
-
const createBasicOperator = (symbol: string) => {
|
|
32
|
-
return (key: string, paramIndex: number) => {
|
|
33
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
34
|
-
return {
|
|
35
|
-
sql: `CASE
|
|
36
|
-
WHEN $${paramIndex}::text IS NULL THEN metadata#>>'{${jsonPathKey}}' IS ${symbol === '=' ? '' : 'NOT'} NULL
|
|
37
|
-
ELSE metadata#>>'{${jsonPathKey}}' ${symbol} $${paramIndex}::text
|
|
38
|
-
END`,
|
|
39
|
-
needsValue: true,
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const createNumericOperator = (symbol: string) => {
|
|
45
|
-
return (key: string, paramIndex: number) => {
|
|
46
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
47
|
-
return {
|
|
48
|
-
sql: `(metadata#>>'{${jsonPathKey}}')::numeric ${symbol} $${paramIndex}`,
|
|
49
|
-
needsValue: true,
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
function buildElemMatchConditions(value: any, paramIndex: number): { sql: string; values: any[] } {
|
|
55
|
-
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
56
|
-
throw new Error('$elemMatch requires an object with conditions');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const conditions: string[] = [];
|
|
60
|
-
const values: any[] = [];
|
|
61
|
-
|
|
62
|
-
Object.entries(value).forEach(([field, val]) => {
|
|
63
|
-
const nextParamIndex = paramIndex + values.length;
|
|
64
|
-
|
|
65
|
-
let paramOperator;
|
|
66
|
-
let paramKey;
|
|
67
|
-
let paramValue;
|
|
68
|
-
|
|
69
|
-
if (field.startsWith('$')) {
|
|
70
|
-
paramOperator = field;
|
|
71
|
-
paramKey = '';
|
|
72
|
-
paramValue = val;
|
|
73
|
-
} else if (typeof val === 'object' && !Array.isArray(val)) {
|
|
74
|
-
const [op, opValue] = Object.entries(val || {})[0] || [];
|
|
75
|
-
paramOperator = op;
|
|
76
|
-
paramKey = field;
|
|
77
|
-
paramValue = opValue;
|
|
78
|
-
} else {
|
|
79
|
-
paramOperator = '$eq';
|
|
80
|
-
paramKey = field;
|
|
81
|
-
paramValue = val;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const operatorFn = FILTER_OPERATORS[paramOperator as OperatorType];
|
|
85
|
-
if (!operatorFn) {
|
|
86
|
-
throw new Error(`Invalid operator: ${paramOperator}`);
|
|
87
|
-
}
|
|
88
|
-
const result = operatorFn(paramKey, nextParamIndex, paramValue);
|
|
89
|
-
|
|
90
|
-
const sql = result.sql.replaceAll('metadata#>>', 'elem#>>');
|
|
91
|
-
conditions.push(sql);
|
|
92
|
-
if (result.needsValue) {
|
|
93
|
-
values.push(paramValue);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
sql: conditions.join(' AND '),
|
|
99
|
-
values,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Define all filter operators
|
|
104
|
-
const FILTER_OPERATORS: Record<OperatorType, OperatorFn> = {
|
|
105
|
-
$eq: createBasicOperator('='),
|
|
106
|
-
$ne: createBasicOperator('!='),
|
|
107
|
-
$gt: createNumericOperator('>'),
|
|
108
|
-
$gte: createNumericOperator('>='),
|
|
109
|
-
$lt: createNumericOperator('<'),
|
|
110
|
-
$lte: createNumericOperator('<='),
|
|
111
|
-
|
|
112
|
-
// Array Operators
|
|
113
|
-
$in: (key, paramIndex) => {
|
|
114
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
115
|
-
return {
|
|
116
|
-
sql: `(
|
|
117
|
-
CASE
|
|
118
|
-
WHEN jsonb_typeof(metadata->'${jsonPathKey}') = 'array' THEN
|
|
119
|
-
EXISTS (
|
|
120
|
-
SELECT 1 FROM jsonb_array_elements_text(metadata->'${jsonPathKey}') as elem
|
|
121
|
-
WHERE elem = ANY($${paramIndex}::text[])
|
|
122
|
-
)
|
|
123
|
-
ELSE metadata#>>'{${jsonPathKey}}' = ANY($${paramIndex}::text[])
|
|
124
|
-
END
|
|
125
|
-
)`,
|
|
126
|
-
needsValue: true,
|
|
127
|
-
};
|
|
128
|
-
},
|
|
129
|
-
$nin: (key, paramIndex) => {
|
|
130
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
131
|
-
return {
|
|
132
|
-
sql: `(
|
|
133
|
-
CASE
|
|
134
|
-
WHEN jsonb_typeof(metadata->'${jsonPathKey}') = 'array' THEN
|
|
135
|
-
NOT EXISTS (
|
|
136
|
-
SELECT 1 FROM jsonb_array_elements_text(metadata->'${jsonPathKey}') as elem
|
|
137
|
-
WHERE elem = ANY($${paramIndex}::text[])
|
|
138
|
-
)
|
|
139
|
-
ELSE metadata#>>'{${jsonPathKey}}' != ALL($${paramIndex}::text[])
|
|
140
|
-
END
|
|
141
|
-
)`,
|
|
142
|
-
needsValue: true,
|
|
143
|
-
};
|
|
144
|
-
},
|
|
145
|
-
$all: (key, paramIndex) => {
|
|
146
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
147
|
-
return {
|
|
148
|
-
sql: `CASE WHEN array_length($${paramIndex}::text[], 1) IS NULL THEN false
|
|
149
|
-
ELSE (metadata#>'{${jsonPathKey}}')::jsonb ?& $${paramIndex}::text[] END`,
|
|
150
|
-
needsValue: true,
|
|
151
|
-
};
|
|
152
|
-
},
|
|
153
|
-
$elemMatch: (key: string, paramIndex: number, value: any): FilterOperator => {
|
|
154
|
-
const { sql, values } = buildElemMatchConditions(value, paramIndex);
|
|
155
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
156
|
-
return {
|
|
157
|
-
sql: `(
|
|
158
|
-
CASE
|
|
159
|
-
WHEN jsonb_typeof(metadata->'${jsonPathKey}') = 'array' THEN
|
|
160
|
-
EXISTS (
|
|
161
|
-
SELECT 1
|
|
162
|
-
FROM jsonb_array_elements(metadata->'${jsonPathKey}') as elem
|
|
163
|
-
WHERE ${sql}
|
|
164
|
-
)
|
|
165
|
-
ELSE FALSE
|
|
166
|
-
END
|
|
167
|
-
)`,
|
|
168
|
-
needsValue: true,
|
|
169
|
-
transformValue: () => values,
|
|
170
|
-
};
|
|
171
|
-
},
|
|
172
|
-
// Element Operators
|
|
173
|
-
$exists: key => {
|
|
174
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
175
|
-
return {
|
|
176
|
-
sql: `metadata ? '${jsonPathKey}'`,
|
|
177
|
-
needsValue: false,
|
|
178
|
-
};
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
// Logical Operators
|
|
182
|
-
$and: key => ({ sql: `(${key})`, needsValue: false }),
|
|
183
|
-
$or: key => ({ sql: `(${key})`, needsValue: false }),
|
|
184
|
-
$not: key => ({ sql: `NOT (${key})`, needsValue: false }),
|
|
185
|
-
$nor: key => ({ sql: `NOT (${key})`, needsValue: false }),
|
|
186
|
-
|
|
187
|
-
// Regex Operators
|
|
188
|
-
$regex: (key, paramIndex) => {
|
|
189
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
190
|
-
return {
|
|
191
|
-
sql: `metadata#>>'{${jsonPathKey}}' ~ $${paramIndex}`,
|
|
192
|
-
needsValue: true,
|
|
193
|
-
};
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
$contains: (key, paramIndex, value: any) => {
|
|
197
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
198
|
-
let sql;
|
|
199
|
-
if (Array.isArray(value)) {
|
|
200
|
-
sql = `(metadata->'${jsonPathKey}') ?& $${paramIndex}`;
|
|
201
|
-
} else if (typeof value === 'string') {
|
|
202
|
-
sql = `metadata->>'${jsonPathKey}' ILIKE '%' || $${paramIndex} || '%' ESCAPE '\\'`;
|
|
203
|
-
} else {
|
|
204
|
-
sql = `metadata->>'${jsonPathKey}' = $${paramIndex}`;
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
sql,
|
|
208
|
-
needsValue: true,
|
|
209
|
-
transformValue: () =>
|
|
210
|
-
Array.isArray(value) ? value.map(String) : typeof value === 'string' ? escapeLikePattern(value) : value,
|
|
211
|
-
};
|
|
212
|
-
},
|
|
213
|
-
/**
|
|
214
|
-
* $objectContains: Postgres-only operator for true JSONB object containment.
|
|
215
|
-
* Usage: { field: { $objectContains: { ...subobject } } }
|
|
216
|
-
*/
|
|
217
|
-
// $objectContains: (key, paramIndex) => ({
|
|
218
|
-
// sql: `metadata @> $${paramIndex}::jsonb`,
|
|
219
|
-
// needsValue: true,
|
|
220
|
-
// transformValue: value => {
|
|
221
|
-
// const parts = key.split('.');
|
|
222
|
-
// return JSON.stringify(parts.reduceRight((value, key) => ({ [key]: value }), value));
|
|
223
|
-
// },
|
|
224
|
-
// }),
|
|
225
|
-
$size: (key: string, paramIndex: number) => {
|
|
226
|
-
const jsonPathKey = parseJsonPathKey(key);
|
|
227
|
-
return {
|
|
228
|
-
sql: `(
|
|
229
|
-
CASE
|
|
230
|
-
WHEN jsonb_typeof(metadata#>'{${jsonPathKey}}') = 'array' THEN
|
|
231
|
-
jsonb_array_length(metadata#>'{${jsonPathKey}}') = $${paramIndex}
|
|
232
|
-
ELSE FALSE
|
|
233
|
-
END
|
|
234
|
-
)`,
|
|
235
|
-
needsValue: true,
|
|
236
|
-
};
|
|
237
|
-
},
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
interface FilterResult {
|
|
241
|
-
sql: string;
|
|
242
|
-
values: any[];
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const parseJsonPathKey = (key: string) => {
|
|
246
|
-
const parsedKey = key !== '' ? parseFieldKey(key) : '';
|
|
247
|
-
return parsedKey.replace(/\./g, ',');
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
function escapeLikePattern(str: string): string {
|
|
251
|
-
return str.replace(/([%_\\])/g, '\\$1');
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export function buildFilterQuery(filter: PGVectorFilter, minScore: number, topK: number): FilterResult {
|
|
255
|
-
const values = [minScore, topK];
|
|
256
|
-
|
|
257
|
-
function buildCondition(key: string, value: any, parentPath: string): string {
|
|
258
|
-
// Handle logical operators ($and/$or)
|
|
259
|
-
if (['$and', '$or', '$not', '$nor'].includes(key)) {
|
|
260
|
-
return handleLogicalOperator(key as '$and' | '$or' | '$not' | '$nor', value, parentPath);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// If condition is not a FilterCondition object, assume it's an equality check
|
|
264
|
-
if (!value || typeof value !== 'object') {
|
|
265
|
-
values.push(value);
|
|
266
|
-
return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Handle operator conditions
|
|
270
|
-
const [[operator, operatorValue] = []] = Object.entries(value);
|
|
271
|
-
|
|
272
|
-
// Special handling for nested $not
|
|
273
|
-
if (operator === '$not') {
|
|
274
|
-
const entries = Object.entries(operatorValue as Record<string, unknown>);
|
|
275
|
-
const conditions = entries
|
|
276
|
-
.map(([nestedOp, nestedValue]) => {
|
|
277
|
-
if (!FILTER_OPERATORS[nestedOp as OperatorType]) {
|
|
278
|
-
throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
|
|
279
|
-
}
|
|
280
|
-
const operatorFn = FILTER_OPERATORS[nestedOp as OperatorType]!;
|
|
281
|
-
const operatorResult = operatorFn(key, values.length + 1, nestedValue);
|
|
282
|
-
if (operatorResult.needsValue) {
|
|
283
|
-
values.push(nestedValue as number);
|
|
284
|
-
}
|
|
285
|
-
return operatorResult.sql;
|
|
286
|
-
})
|
|
287
|
-
.join(' AND ');
|
|
288
|
-
|
|
289
|
-
return `NOT (${conditions})`;
|
|
290
|
-
}
|
|
291
|
-
const operatorFn = FILTER_OPERATORS[operator as OperatorType]!;
|
|
292
|
-
const operatorResult = operatorFn(key, values.length + 1, operatorValue);
|
|
293
|
-
if (operatorResult.needsValue) {
|
|
294
|
-
const transformedValue = operatorResult.transformValue ? operatorResult.transformValue() : operatorValue;
|
|
295
|
-
if (Array.isArray(transformedValue) && operator === '$elemMatch') {
|
|
296
|
-
values.push(...transformedValue);
|
|
297
|
-
} else {
|
|
298
|
-
values.push(transformedValue);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return operatorResult.sql;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function handleLogicalOperator(
|
|
305
|
-
key: '$and' | '$or' | '$not' | '$nor',
|
|
306
|
-
value: VectorFilter[],
|
|
307
|
-
parentPath: string,
|
|
308
|
-
): string {
|
|
309
|
-
if (key === '$not') {
|
|
310
|
-
// For top-level $not
|
|
311
|
-
const entries = Object.entries(value);
|
|
312
|
-
const conditions = entries
|
|
313
|
-
.map(([fieldKey, fieldValue]) => buildCondition(fieldKey, fieldValue, key))
|
|
314
|
-
.join(' AND ');
|
|
315
|
-
return `NOT (${conditions})`;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Handle empty conditions
|
|
319
|
-
if (!value || value.length === 0) {
|
|
320
|
-
switch (key) {
|
|
321
|
-
case '$and':
|
|
322
|
-
case '$nor':
|
|
323
|
-
return 'true'; // Empty $and/$nor match everything
|
|
324
|
-
case '$or':
|
|
325
|
-
return 'false'; // Empty $or matches nothing
|
|
326
|
-
default:
|
|
327
|
-
return 'true';
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const joinOperator = key === '$or' || key === '$nor' ? 'OR' : 'AND';
|
|
332
|
-
const conditions = value.map((f: VectorFilter) => {
|
|
333
|
-
const entries = Object.entries(f || {});
|
|
334
|
-
if (entries.length === 0) return '';
|
|
335
|
-
|
|
336
|
-
const [firstKey, firstValue] = entries[0] || [];
|
|
337
|
-
if (['$and', '$or', '$not', '$nor'].includes(firstKey as string)) {
|
|
338
|
-
return buildCondition(firstKey as string, firstValue, parentPath);
|
|
339
|
-
}
|
|
340
|
-
return entries.map(([k, v]) => buildCondition(k, v, parentPath)).join(` ${joinOperator} `);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
const joined = conditions.join(` ${joinOperator} `);
|
|
344
|
-
const operatorFn = FILTER_OPERATORS[key]!;
|
|
345
|
-
return operatorFn(joined, 0, value).sql;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (!filter) {
|
|
349
|
-
return { sql: '', values };
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const conditions = Object.entries(filter)
|
|
353
|
-
.map(([key, value]) => buildCondition(key, value, ''))
|
|
354
|
-
.filter(Boolean)
|
|
355
|
-
.join(' AND ');
|
|
356
|
-
|
|
357
|
-
return { sql: conditions ? `WHERE ${conditions}` : '', values };
|
|
358
|
-
}
|
package/src/vector/types.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type IndexType = 'ivfflat' | 'hnsw' | 'flat';
|
|
2
|
-
|
|
3
|
-
interface IVFConfig {
|
|
4
|
-
lists?: number;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
interface HNSWConfig {
|
|
8
|
-
m?: number; // Max number of connections (default: 16)
|
|
9
|
-
efConstruction?: number; // Build-time complexity (default: 64)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface IndexConfig {
|
|
13
|
-
type?: IndexType;
|
|
14
|
-
ivf?: IVFConfig;
|
|
15
|
-
hnsw?: HNSWConfig;
|
|
16
|
-
}
|