supalite 0.8.1 → 0.8.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 +12 -0
- package/README.md +17 -1
- package/SPEC.md +5 -1
- package/dist/query-builder.d.ts +4 -0
- package/dist/query-builder.js +226 -82
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.2] - 2026-02-23
|
|
4
|
+
|
|
5
|
+
### ✨ Added
|
|
6
|
+
- `or()`에서 중첩 `and(...)` / `or(...)` 그룹 파싱을 지원합니다. (PostgREST-style)
|
|
7
|
+
- `or()`에서 `in.(...)` 연산자를 지원합니다.
|
|
8
|
+
- `or()`에서 `not.*` 연산자(`not.eq`, `not.ilike`, `not.is`, `not.in` 등)를 지원합니다.
|
|
9
|
+
- 중첩 `or()` 전용 회귀 테스트 파일을 추가해 복합 케이스를 대폭 확장했습니다.
|
|
10
|
+
|
|
11
|
+
### 🐞 Fixed
|
|
12
|
+
- `or('...,and(...)')` 구문이 `and(created_at`를 컬럼으로 오해해 SQL 에러를 내던 문제를 수정했습니다.
|
|
13
|
+
- 괄호/따옴표가 깨진 `or()` 입력에서 명확한 파서 에러를 반환하도록 개선했습니다.
|
|
14
|
+
|
|
3
15
|
## [0.8.1] - 2026-02-03
|
|
4
16
|
|
|
5
17
|
### 🐞 Fixed
|
package/README.md
CHANGED
|
@@ -533,6 +533,18 @@ const { data: credits } = await client
|
|
|
533
533
|
.eq('wallet_id', 123)
|
|
534
534
|
.gt('amount', 0)
|
|
535
535
|
.or('valid_until.is.null,valid_until.gt.now()');
|
|
536
|
+
|
|
537
|
+
// Nested and(...) inside or(...) (PostgREST-style keyset pattern)
|
|
538
|
+
const { data: images } = await client
|
|
539
|
+
.from('priv_images')
|
|
540
|
+
.select('id,created_at')
|
|
541
|
+
.or('created_at.lt.2026-02-13T09:09:32.000Z,and(created_at.eq.2026-02-13T09:09:32.000Z,id.lt.1462)');
|
|
542
|
+
|
|
543
|
+
// in / not.* inside or()
|
|
544
|
+
const { data: users } = await client
|
|
545
|
+
.from('users')
|
|
546
|
+
.select('*')
|
|
547
|
+
.or('and(id.in.(1,2,3),status.not.eq.inactive)');
|
|
536
548
|
```
|
|
537
549
|
|
|
538
550
|
### Sorting/Pagination
|
|
@@ -1032,7 +1044,11 @@ await client.close();
|
|
|
1032
1044
|
- `is(column, value)`: IS
|
|
1033
1045
|
- `not(column, operator, value)`: currently only `not('column', 'is', null)` is supported
|
|
1034
1046
|
- `contains(column, value)`: array/JSON contains
|
|
1035
|
-
- `or(conditions)`: OR condition string (ops: eq/neq/like/ilike/gt/gte/lt/lte/is
|
|
1047
|
+
- `or(conditions)`: OR condition string (ops: eq/neq/like/ilike/gt/gte/lt/lte/is/in + `not.*`, `now()` is inlined as `NOW()`)
|
|
1048
|
+
- Supports nested `and(...)` and `or(...)` groups (PostgREST-style), e.g. `created_at.lt.ts,and(created_at.eq.ts,id.lt.1462)`
|
|
1049
|
+
- Supports `in.(...)` and negated ops such as `status.not.eq.inactive`, `id.not.in.(1,2,3)`, `deleted_at.not.is.null`
|
|
1050
|
+
- Quote values to include dots/commas (e.g. `name.eq."last, first"`)
|
|
1051
|
+
- Related table filters are not supported inside `or()`
|
|
1036
1052
|
|
|
1037
1053
|
### Other methods
|
|
1038
1054
|
|
package/SPEC.md
CHANGED
|
@@ -79,11 +79,15 @@ Notes:
|
|
|
79
79
|
- `in(col, [..., null])` emits `("col" IN (...) OR "col" IS NULL)`; if only NULLs are provided, it emits `IS NULL`.
|
|
80
80
|
- `contains` uses `@>`.
|
|
81
81
|
- `or()` expects `col.op.value` segments separated by commas.
|
|
82
|
-
-
|
|
82
|
+
- Nested `and(...)` and `or(...)` groups are supported inside `or()` (e.g. `created_at.lt.ts,and(created_at.eq.ts,id.lt.1462)`).
|
|
83
|
+
- Supported ops: `eq`, `neq`, `like`, `ilike`, `gt`, `gte`, `lt`, `lte`, `is`, `in`.
|
|
84
|
+
- Negated forms are supported via `not.*` (e.g. `status.not.eq.inactive`, `deleted_at.not.is.null`, `id.not.in.(1,2)`).
|
|
83
85
|
- `value` is treated as a literal string; `null` maps to SQL NULL; numeric strings are kept as strings.
|
|
84
86
|
- `is.null` uses `IS NULL` without a placeholder.
|
|
87
|
+
- `in.(...)` values are parsed as a parenthesized list; `null` entries are handled as `IS NULL`/`IS NOT NULL` branches.
|
|
85
88
|
- `now()` is inlined as `NOW()` for comparison operators (no placeholder).
|
|
86
89
|
- Quote values to include dots/commas (e.g. `name.eq."last, first"`).
|
|
90
|
+
- Malformed nested expressions throw clear parser errors (for example, unbalanced parentheses).
|
|
87
91
|
|
|
88
92
|
### 4.3 Ordering and pagination
|
|
89
93
|
- `order('col')` defaults to `ASC`.
|
package/dist/query-builder.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export declare class QueryBuilder<T extends DatabaseSchema, S extends SchemaName
|
|
|
32
32
|
private splitOrConditions;
|
|
33
33
|
private splitOrCondition;
|
|
34
34
|
private unescapeOrValue;
|
|
35
|
+
private normalizeOrLiteralValue;
|
|
36
|
+
private parseOrInValues;
|
|
37
|
+
private buildOrLeafClause;
|
|
38
|
+
private parseOrSegment;
|
|
35
39
|
private parseSelection;
|
|
36
40
|
private quoteIdentifier;
|
|
37
41
|
private quoteColumn;
|
package/dist/query-builder.js
CHANGED
|
@@ -56,6 +56,7 @@ class QueryBuilder {
|
|
|
56
56
|
let current = '';
|
|
57
57
|
let inQuotes = false;
|
|
58
58
|
let escaped = false;
|
|
59
|
+
let depth = 0;
|
|
59
60
|
for (let i = 0; i < input.length; i += 1) {
|
|
60
61
|
const char = input[i];
|
|
61
62
|
if (escaped) {
|
|
@@ -73,7 +74,22 @@ class QueryBuilder {
|
|
|
73
74
|
current += char;
|
|
74
75
|
continue;
|
|
75
76
|
}
|
|
76
|
-
if (
|
|
77
|
+
if (!inQuotes) {
|
|
78
|
+
if (char === '(') {
|
|
79
|
+
depth += 1;
|
|
80
|
+
current += char;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (char === ')') {
|
|
84
|
+
if (depth === 0) {
|
|
85
|
+
throw new Error('Malformed or() condition: unexpected closing parenthesis.');
|
|
86
|
+
}
|
|
87
|
+
depth -= 1;
|
|
88
|
+
current += char;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (char === ',' && !inQuotes && depth === 0) {
|
|
77
93
|
if (current.trim()) {
|
|
78
94
|
parts.push(current.trim());
|
|
79
95
|
}
|
|
@@ -82,6 +98,12 @@ class QueryBuilder {
|
|
|
82
98
|
}
|
|
83
99
|
current += char;
|
|
84
100
|
}
|
|
101
|
+
if (inQuotes) {
|
|
102
|
+
throw new Error('Malformed or() condition: unterminated double quote.');
|
|
103
|
+
}
|
|
104
|
+
if (depth !== 0) {
|
|
105
|
+
throw new Error('Malformed or() condition: unbalanced parentheses.');
|
|
106
|
+
}
|
|
85
107
|
if (current.trim()) {
|
|
86
108
|
parts.push(current.trim());
|
|
87
109
|
}
|
|
@@ -90,8 +112,7 @@ class QueryBuilder {
|
|
|
90
112
|
splitOrCondition(condition) {
|
|
91
113
|
let inQuotes = false;
|
|
92
114
|
let escaped = false;
|
|
93
|
-
|
|
94
|
-
let secondDot = -1;
|
|
115
|
+
const dotPositions = [];
|
|
95
116
|
for (let i = 0; i < condition.length; i += 1) {
|
|
96
117
|
const char = condition[i];
|
|
97
118
|
if (escaped) {
|
|
@@ -107,18 +128,37 @@ class QueryBuilder {
|
|
|
107
128
|
continue;
|
|
108
129
|
}
|
|
109
130
|
if (char === '.' && !inQuotes) {
|
|
110
|
-
|
|
111
|
-
firstDot = i;
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
secondDot = i;
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
131
|
+
dotPositions.push(i);
|
|
117
132
|
}
|
|
118
133
|
}
|
|
119
|
-
if (
|
|
134
|
+
if (dotPositions.length < 2) {
|
|
120
135
|
return null;
|
|
121
136
|
}
|
|
137
|
+
const validOperators = new Set(['eq', 'neq', 'ilike', 'like', 'gt', 'gte', 'lt', 'lte', 'is', 'in']);
|
|
138
|
+
for (let left = 0; left < dotPositions.length - 1; left += 1) {
|
|
139
|
+
for (let right = left + 1; right < dotPositions.length; right += 1) {
|
|
140
|
+
const firstDot = dotPositions[left];
|
|
141
|
+
const secondDot = dotPositions[right];
|
|
142
|
+
const field = condition.slice(0, firstDot).trim();
|
|
143
|
+
const op = condition.slice(firstDot + 1, secondDot).trim();
|
|
144
|
+
const value = condition.slice(secondDot + 1).trim();
|
|
145
|
+
if (field && op && value && validOperators.has(op)) {
|
|
146
|
+
return { field, op, value };
|
|
147
|
+
}
|
|
148
|
+
if (op === 'not') {
|
|
149
|
+
for (let third = right + 1; third < dotPositions.length; third += 1) {
|
|
150
|
+
const thirdDot = dotPositions[third];
|
|
151
|
+
const notOp = condition.slice(secondDot + 1, thirdDot).trim();
|
|
152
|
+
const notValue = condition.slice(thirdDot + 1).trim();
|
|
153
|
+
if (field && notOp && notValue && validOperators.has(notOp)) {
|
|
154
|
+
return { field, op: `not.${notOp}`, value: notValue };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const firstDot = dotPositions[0];
|
|
161
|
+
const secondDot = dotPositions[1];
|
|
122
162
|
const field = condition.slice(0, firstDot).trim();
|
|
123
163
|
const op = condition.slice(firstDot + 1, secondDot).trim();
|
|
124
164
|
const value = condition.slice(secondDot + 1).trim();
|
|
@@ -127,6 +167,180 @@ class QueryBuilder {
|
|
|
127
167
|
unescapeOrValue(value) {
|
|
128
168
|
return value.replace(/\\([\\\".,])/g, '$1');
|
|
129
169
|
}
|
|
170
|
+
normalizeOrLiteralValue(value) {
|
|
171
|
+
if (value === 'null') {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
if (!isNaN(Number(value))) {
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
if (value.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
parseOrInValues(value) {
|
|
183
|
+
if (!value.startsWith('(') || !value.endsWith(')')) {
|
|
184
|
+
throw new Error(`Invalid or() IN value: "${value}". Expected parenthesized list like "(a,b)".`);
|
|
185
|
+
}
|
|
186
|
+
const inner = value.slice(1, -1).trim();
|
|
187
|
+
if (!inner) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
return this.splitOrConditions(inner).map((raw) => {
|
|
191
|
+
let item = raw.trim();
|
|
192
|
+
if (item.startsWith('"') && item.endsWith('"')) {
|
|
193
|
+
item = item.slice(1, -1);
|
|
194
|
+
}
|
|
195
|
+
return this.unescapeOrValue(item);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
buildOrLeafClause(condition) {
|
|
199
|
+
const parsed = this.splitOrCondition(condition);
|
|
200
|
+
if (!parsed) {
|
|
201
|
+
throw new Error(`Invalid or() condition segment: "${condition}". Expected "column.operator.value".`);
|
|
202
|
+
}
|
|
203
|
+
const { field, op, value } = parsed;
|
|
204
|
+
if (!field || !op) {
|
|
205
|
+
throw new Error(`Invalid or() condition segment: "${condition}". Expected "column.operator.value".`);
|
|
206
|
+
}
|
|
207
|
+
if (field.includes('.')) {
|
|
208
|
+
throw new Error('or() does not support related table filters.');
|
|
209
|
+
}
|
|
210
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field)) {
|
|
211
|
+
throw new Error(`Invalid or() column: "${field}". Use "column.operator.value" or nested and(...)/or(...).`);
|
|
212
|
+
}
|
|
213
|
+
const isNegated = op.startsWith('not.');
|
|
214
|
+
const baseOp = isNegated ? op.slice(4) : op;
|
|
215
|
+
const validOperators = ['eq', 'neq', 'ilike', 'like', 'gt', 'gte', 'lt', 'lte', 'is', 'in'];
|
|
216
|
+
if (!validOperators.includes(baseOp)) {
|
|
217
|
+
throw new Error(`Invalid operator: ${baseOp}`);
|
|
218
|
+
}
|
|
219
|
+
let normalizedValue = value;
|
|
220
|
+
if (normalizedValue.startsWith('"') && normalizedValue.endsWith('"')) {
|
|
221
|
+
normalizedValue = normalizedValue.slice(1, -1);
|
|
222
|
+
}
|
|
223
|
+
normalizedValue = this.unescapeOrValue(normalizedValue);
|
|
224
|
+
const isNullValue = normalizedValue === 'null';
|
|
225
|
+
const isNowValue = typeof normalizedValue === 'string' && normalizedValue.toLowerCase() === 'now()';
|
|
226
|
+
const quotedField = this.quoteColumn(field);
|
|
227
|
+
const buildPlaceholderClause = (sqlOp) => {
|
|
228
|
+
this.whereValues.push(this.normalizeOrLiteralValue(normalizedValue));
|
|
229
|
+
const paramIndex = this.whereValues.length;
|
|
230
|
+
return `${quotedField} ${sqlOp} $${paramIndex}`;
|
|
231
|
+
};
|
|
232
|
+
const buildClause = (sqlOp) => {
|
|
233
|
+
if (isNowValue) {
|
|
234
|
+
return `${quotedField} ${sqlOp} NOW()`;
|
|
235
|
+
}
|
|
236
|
+
return buildPlaceholderClause(sqlOp);
|
|
237
|
+
};
|
|
238
|
+
if (baseOp === 'in') {
|
|
239
|
+
const parsedValues = this.parseOrInValues(normalizedValue);
|
|
240
|
+
const hasNull = parsedValues.includes('null');
|
|
241
|
+
const nonNullValues = parsedValues.filter((item) => item !== 'null');
|
|
242
|
+
const pushPlaceholders = (items) => {
|
|
243
|
+
const placeholders = items.map((item) => {
|
|
244
|
+
this.whereValues.push(this.normalizeOrLiteralValue(item));
|
|
245
|
+
return `$${this.whereValues.length}`;
|
|
246
|
+
});
|
|
247
|
+
return placeholders.join(',');
|
|
248
|
+
};
|
|
249
|
+
if (!isNegated) {
|
|
250
|
+
if (nonNullValues.length === 0 && hasNull) {
|
|
251
|
+
return `${quotedField} IS NULL`;
|
|
252
|
+
}
|
|
253
|
+
if (nonNullValues.length === 0) {
|
|
254
|
+
return 'FALSE';
|
|
255
|
+
}
|
|
256
|
+
const inList = pushPlaceholders(nonNullValues);
|
|
257
|
+
if (hasNull) {
|
|
258
|
+
return `(${quotedField} IN (${inList}) OR ${quotedField} IS NULL)`;
|
|
259
|
+
}
|
|
260
|
+
return `${quotedField} IN (${inList})`;
|
|
261
|
+
}
|
|
262
|
+
if (nonNullValues.length === 0 && hasNull) {
|
|
263
|
+
return `${quotedField} IS NOT NULL`;
|
|
264
|
+
}
|
|
265
|
+
if (nonNullValues.length === 0) {
|
|
266
|
+
return 'TRUE';
|
|
267
|
+
}
|
|
268
|
+
const notInList = pushPlaceholders(nonNullValues);
|
|
269
|
+
if (hasNull) {
|
|
270
|
+
return `(${quotedField} NOT IN (${notInList}) AND ${quotedField} IS NOT NULL)`;
|
|
271
|
+
}
|
|
272
|
+
return `${quotedField} NOT IN (${notInList})`;
|
|
273
|
+
}
|
|
274
|
+
const resolveSqlOperator = (operator) => {
|
|
275
|
+
if (!isNegated) {
|
|
276
|
+
return operator;
|
|
277
|
+
}
|
|
278
|
+
switch (operator) {
|
|
279
|
+
case '=':
|
|
280
|
+
return '!=';
|
|
281
|
+
case '!=':
|
|
282
|
+
return '=';
|
|
283
|
+
case 'LIKE':
|
|
284
|
+
return 'NOT LIKE';
|
|
285
|
+
case 'ILIKE':
|
|
286
|
+
return 'NOT ILIKE';
|
|
287
|
+
case '>':
|
|
288
|
+
return '<=';
|
|
289
|
+
case '>=':
|
|
290
|
+
return '<';
|
|
291
|
+
case '<':
|
|
292
|
+
return '>=';
|
|
293
|
+
case '<=':
|
|
294
|
+
return '>';
|
|
295
|
+
case 'IS':
|
|
296
|
+
return 'IS NOT';
|
|
297
|
+
default:
|
|
298
|
+
return operator;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
switch (baseOp) {
|
|
302
|
+
case 'eq':
|
|
303
|
+
return buildClause(resolveSqlOperator('='));
|
|
304
|
+
case 'neq':
|
|
305
|
+
return buildClause(resolveSqlOperator('!='));
|
|
306
|
+
case 'ilike':
|
|
307
|
+
return buildClause(resolveSqlOperator('ILIKE'));
|
|
308
|
+
case 'like':
|
|
309
|
+
return buildClause(resolveSqlOperator('LIKE'));
|
|
310
|
+
case 'gt':
|
|
311
|
+
return buildClause(resolveSqlOperator('>'));
|
|
312
|
+
case 'gte':
|
|
313
|
+
return buildClause(resolveSqlOperator('>='));
|
|
314
|
+
case 'lt':
|
|
315
|
+
return buildClause(resolveSqlOperator('<'));
|
|
316
|
+
case 'lte':
|
|
317
|
+
return buildClause(resolveSqlOperator('<='));
|
|
318
|
+
case 'is':
|
|
319
|
+
if (isNullValue) {
|
|
320
|
+
return isNegated ? `${quotedField} IS NOT NULL` : `${quotedField} IS NULL`;
|
|
321
|
+
}
|
|
322
|
+
return buildPlaceholderClause(resolveSqlOperator('IS'));
|
|
323
|
+
default:
|
|
324
|
+
throw new Error(`Invalid operator: ${baseOp}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
parseOrSegment(condition) {
|
|
328
|
+
const trimmed = condition.trim();
|
|
329
|
+
if (!trimmed) {
|
|
330
|
+
throw new Error('Invalid or() condition: empty segment.');
|
|
331
|
+
}
|
|
332
|
+
const lower = trimmed.toLowerCase();
|
|
333
|
+
if ((lower.startsWith('and(') || lower.startsWith('or(')) && trimmed.endsWith(')')) {
|
|
334
|
+
const useAnd = lower.startsWith('and(');
|
|
335
|
+
const inner = trimmed.slice(trimmed.indexOf('(') + 1, -1).trim();
|
|
336
|
+
const parts = this.splitOrConditions(inner).map((part) => this.parseOrSegment(part));
|
|
337
|
+
if (parts.length === 0) {
|
|
338
|
+
throw new Error(`Invalid or() condition: ${useAnd ? 'and' : 'or'}(...) requires at least one segment.`);
|
|
339
|
+
}
|
|
340
|
+
return `(${parts.join(useAnd ? ' AND ' : ' OR ')})`;
|
|
341
|
+
}
|
|
342
|
+
return this.buildOrLeafClause(trimmed);
|
|
343
|
+
}
|
|
130
344
|
parseSelection(input) {
|
|
131
345
|
const tokens = this.splitTopLevel(input);
|
|
132
346
|
const items = [];
|
|
@@ -447,77 +661,7 @@ class QueryBuilder {
|
|
|
447
661
|
return this;
|
|
448
662
|
}
|
|
449
663
|
or(conditions) {
|
|
450
|
-
const orParts = this.splitOrConditions(conditions).map(condition =>
|
|
451
|
-
const parsed = this.splitOrCondition(condition);
|
|
452
|
-
if (!parsed) {
|
|
453
|
-
return '';
|
|
454
|
-
}
|
|
455
|
-
const { field, op, value } = parsed;
|
|
456
|
-
if (!field || !op) {
|
|
457
|
-
return '';
|
|
458
|
-
}
|
|
459
|
-
if (field.includes('.')) {
|
|
460
|
-
throw new Error('or() does not support related table filters.');
|
|
461
|
-
}
|
|
462
|
-
const validOperators = ['eq', 'neq', 'ilike', 'like', 'gt', 'gte', 'lt', 'lte', 'is'];
|
|
463
|
-
if (!validOperators.includes(op)) {
|
|
464
|
-
throw new Error(`Invalid operator: ${op}`);
|
|
465
|
-
}
|
|
466
|
-
let normalizedValue = value;
|
|
467
|
-
if (normalizedValue.startsWith('"') && normalizedValue.endsWith('"')) {
|
|
468
|
-
normalizedValue = normalizedValue.slice(1, -1);
|
|
469
|
-
}
|
|
470
|
-
normalizedValue = this.unescapeOrValue(normalizedValue);
|
|
471
|
-
const isNullValue = normalizedValue === 'null';
|
|
472
|
-
const isNowValue = typeof normalizedValue === 'string' && normalizedValue.toLowerCase() === 'now()';
|
|
473
|
-
const quotedField = this.quoteColumn(field);
|
|
474
|
-
const buildPlaceholderClause = (sqlOp) => {
|
|
475
|
-
let processedValue = normalizedValue;
|
|
476
|
-
if (isNullValue) {
|
|
477
|
-
processedValue = null;
|
|
478
|
-
}
|
|
479
|
-
else if (typeof normalizedValue === 'string' && !isNaN(Number(normalizedValue))) {
|
|
480
|
-
processedValue = normalizedValue;
|
|
481
|
-
}
|
|
482
|
-
else if (typeof normalizedValue === 'string' && normalizedValue.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
483
|
-
processedValue = normalizedValue;
|
|
484
|
-
}
|
|
485
|
-
this.whereValues.push(processedValue);
|
|
486
|
-
const paramIndex = this.whereValues.length;
|
|
487
|
-
return `${quotedField} ${sqlOp} $${paramIndex}`;
|
|
488
|
-
};
|
|
489
|
-
const buildClause = (sqlOp) => {
|
|
490
|
-
if (isNowValue) {
|
|
491
|
-
return `${quotedField} ${sqlOp} NOW()`;
|
|
492
|
-
}
|
|
493
|
-
return buildPlaceholderClause(sqlOp);
|
|
494
|
-
};
|
|
495
|
-
switch (op) {
|
|
496
|
-
case 'eq':
|
|
497
|
-
return buildClause('=');
|
|
498
|
-
case 'neq':
|
|
499
|
-
return buildClause('!=');
|
|
500
|
-
case 'ilike':
|
|
501
|
-
return buildClause('ILIKE');
|
|
502
|
-
case 'like':
|
|
503
|
-
return buildClause('LIKE');
|
|
504
|
-
case 'gt':
|
|
505
|
-
return buildClause('>');
|
|
506
|
-
case 'gte':
|
|
507
|
-
return buildClause('>=');
|
|
508
|
-
case 'lt':
|
|
509
|
-
return buildClause('<');
|
|
510
|
-
case 'lte':
|
|
511
|
-
return buildClause('<=');
|
|
512
|
-
case 'is':
|
|
513
|
-
if (isNullValue) {
|
|
514
|
-
return `${quotedField} IS NULL`;
|
|
515
|
-
}
|
|
516
|
-
return buildPlaceholderClause('IS');
|
|
517
|
-
default:
|
|
518
|
-
return '';
|
|
519
|
-
}
|
|
520
|
-
}).filter(Boolean);
|
|
664
|
+
const orParts = this.splitOrConditions(conditions).map((condition) => this.parseOrSegment(condition));
|
|
521
665
|
if (orParts.length > 0) {
|
|
522
666
|
this.whereConditions.push(`(${orParts.join(' OR ')})`);
|
|
523
667
|
}
|