@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.
@@ -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
- }
@@ -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
- }