@dockstat/sqlite-wrapper 1.2.6 → 1.2.8
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/LICENSE +373 -373
- package/README.md +99 -66
- package/index.ts +858 -840
- package/package.json +54 -54
- package/query-builder/base.ts +221 -221
- package/query-builder/delete.ts +352 -352
- package/query-builder/index.ts +431 -431
- package/query-builder/insert.ts +249 -249
- package/query-builder/select.ts +358 -358
- package/query-builder/update.ts +278 -278
- package/query-builder/where.ts +307 -307
- package/types.ts +623 -623
package/query-builder/where.ts
CHANGED
|
@@ -1,307 +1,307 @@
|
|
|
1
|
-
import type { SQLQueryBindings } from "bun:sqlite";
|
|
2
|
-
import type { WhereCondition, RegexCondition } from "../types";
|
|
3
|
-
import { BaseQueryBuilder } from "./base";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Mixin class that adds WHERE-related functionality to the QueryBuilder.
|
|
7
|
-
* This includes all conditional filtering methods.
|
|
8
|
-
*/
|
|
9
|
-
export class WhereQueryBuilder<
|
|
10
|
-
T extends Record<string, unknown>,
|
|
11
|
-
> extends BaseQueryBuilder<T> {
|
|
12
|
-
/**
|
|
13
|
-
* Remove existing condition for a column
|
|
14
|
-
* @param column - Column name to check
|
|
15
|
-
* @param operation - Optional operation type (e.g., '=', 'IN', 'BETWEEN')
|
|
16
|
-
*/
|
|
17
|
-
private removeExistingCondition(column: string, operation?: string): void {
|
|
18
|
-
let existingIndex = -1;
|
|
19
|
-
|
|
20
|
-
if (operation) {
|
|
21
|
-
// Look for specific operation
|
|
22
|
-
existingIndex = this.state.whereConditions.findIndex(condition =>
|
|
23
|
-
condition.startsWith(`${String(column)} ${operation}`)
|
|
24
|
-
);
|
|
25
|
-
} else {
|
|
26
|
-
// Look for any condition on this column
|
|
27
|
-
existingIndex = this.state.whereConditions.findIndex(condition =>
|
|
28
|
-
condition.startsWith(`${String(column)} `)
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (existingIndex !== -1) {
|
|
33
|
-
this.state.whereConditions.splice(existingIndex, 1);
|
|
34
|
-
// Only remove params if they exist (some conditions might not have params)
|
|
35
|
-
if (existingIndex < this.state.whereParams.length) {
|
|
36
|
-
this.state.whereParams.splice(existingIndex, 1);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Also remove any regex conditions for this column
|
|
41
|
-
this.state.regexConditions = this.state.regexConditions.filter(
|
|
42
|
-
cond => String(cond.column) !== column
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Add simple equality conditions to the WHERE clause.
|
|
48
|
-
* Handles null values appropriately with IS NULL / IS NOT NULL.
|
|
49
|
-
* Prevents duplicate conditions for the same column.
|
|
50
|
-
*
|
|
51
|
-
* @param conditions - Object with column-value pairs for equality checks
|
|
52
|
-
* @returns this for method chaining
|
|
53
|
-
*/
|
|
54
|
-
where(conditions: WhereCondition<T>): this {
|
|
55
|
-
for (const [column, value] of Object.entries(conditions)) {
|
|
56
|
-
// Remove any existing conditions for this column
|
|
57
|
-
this.removeExistingCondition(column);
|
|
58
|
-
|
|
59
|
-
if (value === null || value === undefined) {
|
|
60
|
-
this.state.whereConditions.push(`${String(column)} IS NULL`);
|
|
61
|
-
} else {
|
|
62
|
-
this.state.whereConditions.push(`${String(column)} = ?`);
|
|
63
|
-
|
|
64
|
-
// Convert JavaScript boolean to SQLite integer (0/1)
|
|
65
|
-
let sqliteValue: SQLQueryBindings = value;
|
|
66
|
-
if (typeof value === 'boolean') {
|
|
67
|
-
sqliteValue = value ? 1 : 0;
|
|
68
|
-
this.getLogger().debug(`Converting boolean value ${value} to ${sqliteValue} for column ${column}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.state.whereParams.push(sqliteValue);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return this;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Add regex conditions. Note: regex conditions are applied client-side
|
|
79
|
-
* after SQL execution due to Bun's SQLite limitations.
|
|
80
|
-
* Prevents duplicate regex conditions for the same column.
|
|
81
|
-
*
|
|
82
|
-
* @param conditions - Object with column-regex pairs
|
|
83
|
-
* @returns this for method chaining
|
|
84
|
-
*/
|
|
85
|
-
whereRgx(conditions: RegexCondition<T>): this {
|
|
86
|
-
for (const [column, value] of Object.entries(conditions)) {
|
|
87
|
-
// Remove any existing conditions for this column
|
|
88
|
-
this.removeExistingCondition(column);
|
|
89
|
-
|
|
90
|
-
if (value instanceof RegExp) {
|
|
91
|
-
this.state.regexConditions.push({
|
|
92
|
-
column: column as keyof T,
|
|
93
|
-
regex: value,
|
|
94
|
-
});
|
|
95
|
-
} else if (typeof value === "string") {
|
|
96
|
-
this.state.regexConditions.push({
|
|
97
|
-
column: column as keyof T,
|
|
98
|
-
regex: new RegExp(value),
|
|
99
|
-
});
|
|
100
|
-
} else if (value !== null && value !== undefined) {
|
|
101
|
-
this.state.whereConditions.push(`${String(column)} = ?`);
|
|
102
|
-
this.state.whereParams.push(value);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return this;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Add a raw SQL WHERE fragment with parameter binding.
|
|
110
|
-
* Note: Raw expressions bypass duplicate checking as they may be complex conditions.
|
|
111
|
-
*
|
|
112
|
-
* @param expr - SQL fragment (without leading WHERE/AND), can use ? placeholders
|
|
113
|
-
* @param params - Values for the placeholders in order
|
|
114
|
-
* @returns this for method chaining
|
|
115
|
-
*/
|
|
116
|
-
whereExpr(expr: string, params: SQLQueryBindings[] = []): this {
|
|
117
|
-
if (!expr || typeof expr !== "string") {
|
|
118
|
-
throw new Error("whereExpr: expr must be a non-empty string");
|
|
119
|
-
}
|
|
120
|
-
// Wrap in parentheses to preserve grouping when combined with other clauses
|
|
121
|
-
this.state.whereConditions.push(`(${expr})`);
|
|
122
|
-
if (params.length) {
|
|
123
|
-
this.state.whereParams.push(...params);
|
|
124
|
-
}
|
|
125
|
-
return this;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Alias for whereExpr for compatibility
|
|
130
|
-
*/
|
|
131
|
-
whereRaw(expr: string, params: SQLQueryBindings[] = []): this {
|
|
132
|
-
return this.whereExpr(expr, params);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Add an IN clause for the given column with proper parameter binding.
|
|
137
|
-
* Replaces any existing conditions for the same column.
|
|
138
|
-
*
|
|
139
|
-
* @param column - Column name to check
|
|
140
|
-
* @param values - Non-empty array of values for the IN clause
|
|
141
|
-
* @returns this for method chaining
|
|
142
|
-
*/
|
|
143
|
-
whereIn(column: keyof T, values: SQLQueryBindings[]): this {
|
|
144
|
-
if (!Array.isArray(values) || values.length === 0) {
|
|
145
|
-
throw new Error("whereIn: values must be a non-empty array");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Remove any existing conditions for this column
|
|
149
|
-
this.removeExistingCondition(String(column), "IN");
|
|
150
|
-
|
|
151
|
-
const placeholders = values.map(() => "?").join(", ");
|
|
152
|
-
this.state.whereConditions.push(`${String(column)} IN (${placeholders})`);
|
|
153
|
-
this.state.whereParams.push(...values);
|
|
154
|
-
return this;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Add a NOT IN clause for the given column with proper parameter binding.
|
|
159
|
-
* Replaces any existing conditions for the same column.
|
|
160
|
-
*
|
|
161
|
-
* @param column - Column name to check
|
|
162
|
-
* @param values - Non-empty array of values for the NOT IN clause
|
|
163
|
-
* @returns this for method chaining
|
|
164
|
-
*/
|
|
165
|
-
whereNotIn(column: keyof T, values: SQLQueryBindings[]): this {
|
|
166
|
-
if (!Array.isArray(values) || values.length === 0) {
|
|
167
|
-
throw new Error("whereNotIn: values must be a non-empty array");
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Remove any existing conditions for this column
|
|
171
|
-
this.removeExistingCondition(String(column), "NOT IN");
|
|
172
|
-
|
|
173
|
-
const placeholders = values.map(() => "?").join(", ");
|
|
174
|
-
this.state.whereConditions.push(`${String(column)} NOT IN (${placeholders})`);
|
|
175
|
-
this.state.whereParams.push(...values);
|
|
176
|
-
return this;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Add a comparison operator condition with proper null handling.
|
|
181
|
-
* Replaces any existing conditions for the same column.
|
|
182
|
-
* Supports: =, !=, <>, <, <=, >, >=, LIKE, GLOB, IS
|
|
183
|
-
*
|
|
184
|
-
* @param column - Column name
|
|
185
|
-
* @param op - Comparison operator
|
|
186
|
-
* @param value - Value to compare (handles null appropriately)
|
|
187
|
-
* @returns this for method chaining
|
|
188
|
-
*/
|
|
189
|
-
whereOp(column: keyof T, op: string, value: SQLQueryBindings): this {
|
|
190
|
-
const normalizedOp = (op ?? "").toUpperCase().trim();
|
|
191
|
-
const allowed = [
|
|
192
|
-
"=",
|
|
193
|
-
"!=",
|
|
194
|
-
"<>",
|
|
195
|
-
"<",
|
|
196
|
-
"<=",
|
|
197
|
-
">",
|
|
198
|
-
">=",
|
|
199
|
-
"LIKE",
|
|
200
|
-
"GLOB",
|
|
201
|
-
"IS",
|
|
202
|
-
"IS NOT",
|
|
203
|
-
];
|
|
204
|
-
|
|
205
|
-
if (!allowed.includes(normalizedOp)) {
|
|
206
|
-
throw new Error(`whereOp: operator "${op}" not supported`);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Handle null special-casing for IS / IS NOT and equality operators
|
|
210
|
-
if (
|
|
211
|
-
(value === null || value === undefined) &&
|
|
212
|
-
(normalizedOp === "=" || normalizedOp === "IS")
|
|
213
|
-
) {
|
|
214
|
-
this.state.whereConditions.push(`${String(column)} IS NULL`);
|
|
215
|
-
return this;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
(value === null || value === undefined) &&
|
|
220
|
-
(normalizedOp === "!=" ||
|
|
221
|
-
normalizedOp === "<>" ||
|
|
222
|
-
normalizedOp === "IS NOT")
|
|
223
|
-
) {
|
|
224
|
-
this.state.whereConditions.push(`${String(column)} IS NOT NULL`);
|
|
225
|
-
return this;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Normal param-bound condition
|
|
229
|
-
this.state.whereConditions.push(`${String(column)} ${normalizedOp} ?`);
|
|
230
|
-
this.state.whereParams.push(value);
|
|
231
|
-
return this;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Add a BETWEEN condition for the given column.
|
|
236
|
-
* Replaces any existing conditions for the same column.
|
|
237
|
-
*
|
|
238
|
-
* @param column - Column name
|
|
239
|
-
* @param min - Minimum value (inclusive)
|
|
240
|
-
* @param max - Maximum value (inclusive)
|
|
241
|
-
* @returns this for method chaining
|
|
242
|
-
*/
|
|
243
|
-
whereBetween(
|
|
244
|
-
column: keyof T,
|
|
245
|
-
min: SQLQueryBindings,
|
|
246
|
-
max: SQLQueryBindings,
|
|
247
|
-
): this {
|
|
248
|
-
// Remove any existing conditions for this column
|
|
249
|
-
this.removeExistingCondition(String(column), "BETWEEN");
|
|
250
|
-
|
|
251
|
-
this.state.whereConditions.push(`${String(column)} BETWEEN ? AND ?`);
|
|
252
|
-
this.state.whereParams.push(min, max);
|
|
253
|
-
return this;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Add a NOT BETWEEN condition for the given column.
|
|
258
|
-
* Replaces any existing conditions for the same column.
|
|
259
|
-
*
|
|
260
|
-
* @param column - Column name
|
|
261
|
-
* @param min - Minimum value (exclusive)
|
|
262
|
-
* @param max - Maximum value (exclusive)
|
|
263
|
-
* @returns this for method chaining
|
|
264
|
-
*/
|
|
265
|
-
whereNotBetween(
|
|
266
|
-
column: keyof T,
|
|
267
|
-
min: SQLQueryBindings,
|
|
268
|
-
max: SQLQueryBindings,
|
|
269
|
-
): this {
|
|
270
|
-
// Remove any existing conditions for this column
|
|
271
|
-
this.removeExistingCondition(String(column), "NOT BETWEEN");
|
|
272
|
-
|
|
273
|
-
this.state.whereConditions.push(`${String(column)} NOT BETWEEN ? AND ?`);
|
|
274
|
-
this.state.whereParams.push(min, max);
|
|
275
|
-
return this;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Add an IS NULL condition for the given column.
|
|
280
|
-
* Replaces any existing conditions for the same column.
|
|
281
|
-
*
|
|
282
|
-
* @param column - Column name
|
|
283
|
-
* @returns this for method chaining
|
|
284
|
-
*/
|
|
285
|
-
whereNull(column: keyof T): this {
|
|
286
|
-
// Remove any existing conditions for this column
|
|
287
|
-
this.removeExistingCondition(String(column));
|
|
288
|
-
|
|
289
|
-
this.state.whereConditions.push(`${String(column)} IS NULL`);
|
|
290
|
-
return this;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Add an IS NOT NULL condition for the given column.
|
|
295
|
-
* Replaces any existing conditions for the same column.
|
|
296
|
-
*
|
|
297
|
-
* @param column - Column name
|
|
298
|
-
* @returns this for method chaining
|
|
299
|
-
*/
|
|
300
|
-
whereNotNull(column: keyof T): this {
|
|
301
|
-
// Remove any existing conditions for this column
|
|
302
|
-
this.removeExistingCondition(String(column));
|
|
303
|
-
|
|
304
|
-
this.state.whereConditions.push(`${String(column)} IS NOT NULL`);
|
|
305
|
-
return this;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
1
|
+
import type { SQLQueryBindings } from "bun:sqlite";
|
|
2
|
+
import type { WhereCondition, RegexCondition } from "../types";
|
|
3
|
+
import { BaseQueryBuilder } from "./base";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mixin class that adds WHERE-related functionality to the QueryBuilder.
|
|
7
|
+
* This includes all conditional filtering methods.
|
|
8
|
+
*/
|
|
9
|
+
export class WhereQueryBuilder<
|
|
10
|
+
T extends Record<string, unknown>,
|
|
11
|
+
> extends BaseQueryBuilder<T> {
|
|
12
|
+
/**
|
|
13
|
+
* Remove existing condition for a column
|
|
14
|
+
* @param column - Column name to check
|
|
15
|
+
* @param operation - Optional operation type (e.g., '=', 'IN', 'BETWEEN')
|
|
16
|
+
*/
|
|
17
|
+
private removeExistingCondition(column: string, operation?: string): void {
|
|
18
|
+
let existingIndex = -1;
|
|
19
|
+
|
|
20
|
+
if (operation) {
|
|
21
|
+
// Look for specific operation
|
|
22
|
+
existingIndex = this.state.whereConditions.findIndex(condition =>
|
|
23
|
+
condition.startsWith(`${String(column)} ${operation}`)
|
|
24
|
+
);
|
|
25
|
+
} else {
|
|
26
|
+
// Look for any condition on this column
|
|
27
|
+
existingIndex = this.state.whereConditions.findIndex(condition =>
|
|
28
|
+
condition.startsWith(`${String(column)} `)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (existingIndex !== -1) {
|
|
33
|
+
this.state.whereConditions.splice(existingIndex, 1);
|
|
34
|
+
// Only remove params if they exist (some conditions might not have params)
|
|
35
|
+
if (existingIndex < this.state.whereParams.length) {
|
|
36
|
+
this.state.whereParams.splice(existingIndex, 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Also remove any regex conditions for this column
|
|
41
|
+
this.state.regexConditions = this.state.regexConditions.filter(
|
|
42
|
+
cond => String(cond.column) !== column
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Add simple equality conditions to the WHERE clause.
|
|
48
|
+
* Handles null values appropriately with IS NULL / IS NOT NULL.
|
|
49
|
+
* Prevents duplicate conditions for the same column.
|
|
50
|
+
*
|
|
51
|
+
* @param conditions - Object with column-value pairs for equality checks
|
|
52
|
+
* @returns this for method chaining
|
|
53
|
+
*/
|
|
54
|
+
where(conditions: WhereCondition<T>): this {
|
|
55
|
+
for (const [column, value] of Object.entries(conditions)) {
|
|
56
|
+
// Remove any existing conditions for this column
|
|
57
|
+
this.removeExistingCondition(column);
|
|
58
|
+
|
|
59
|
+
if (value === null || value === undefined) {
|
|
60
|
+
this.state.whereConditions.push(`${String(column)} IS NULL`);
|
|
61
|
+
} else {
|
|
62
|
+
this.state.whereConditions.push(`${String(column)} = ?`);
|
|
63
|
+
|
|
64
|
+
// Convert JavaScript boolean to SQLite integer (0/1)
|
|
65
|
+
let sqliteValue: SQLQueryBindings = value;
|
|
66
|
+
if (typeof value === 'boolean') {
|
|
67
|
+
sqliteValue = value ? 1 : 0;
|
|
68
|
+
this.getLogger().debug(`Converting boolean value ${value} to ${sqliteValue} for column ${column}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.state.whereParams.push(sqliteValue);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Add regex conditions. Note: regex conditions are applied client-side
|
|
79
|
+
* after SQL execution due to Bun's SQLite limitations.
|
|
80
|
+
* Prevents duplicate regex conditions for the same column.
|
|
81
|
+
*
|
|
82
|
+
* @param conditions - Object with column-regex pairs
|
|
83
|
+
* @returns this for method chaining
|
|
84
|
+
*/
|
|
85
|
+
whereRgx(conditions: RegexCondition<T>): this {
|
|
86
|
+
for (const [column, value] of Object.entries(conditions)) {
|
|
87
|
+
// Remove any existing conditions for this column
|
|
88
|
+
this.removeExistingCondition(column);
|
|
89
|
+
|
|
90
|
+
if (value instanceof RegExp) {
|
|
91
|
+
this.state.regexConditions.push({
|
|
92
|
+
column: column as keyof T,
|
|
93
|
+
regex: value,
|
|
94
|
+
});
|
|
95
|
+
} else if (typeof value === "string") {
|
|
96
|
+
this.state.regexConditions.push({
|
|
97
|
+
column: column as keyof T,
|
|
98
|
+
regex: new RegExp(value),
|
|
99
|
+
});
|
|
100
|
+
} else if (value !== null && value !== undefined) {
|
|
101
|
+
this.state.whereConditions.push(`${String(column)} = ?`);
|
|
102
|
+
this.state.whereParams.push(value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Add a raw SQL WHERE fragment with parameter binding.
|
|
110
|
+
* Note: Raw expressions bypass duplicate checking as they may be complex conditions.
|
|
111
|
+
*
|
|
112
|
+
* @param expr - SQL fragment (without leading WHERE/AND), can use ? placeholders
|
|
113
|
+
* @param params - Values for the placeholders in order
|
|
114
|
+
* @returns this for method chaining
|
|
115
|
+
*/
|
|
116
|
+
whereExpr(expr: string, params: SQLQueryBindings[] = []): this {
|
|
117
|
+
if (!expr || typeof expr !== "string") {
|
|
118
|
+
throw new Error("whereExpr: expr must be a non-empty string");
|
|
119
|
+
}
|
|
120
|
+
// Wrap in parentheses to preserve grouping when combined with other clauses
|
|
121
|
+
this.state.whereConditions.push(`(${expr})`);
|
|
122
|
+
if (params.length) {
|
|
123
|
+
this.state.whereParams.push(...params);
|
|
124
|
+
}
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Alias for whereExpr for compatibility
|
|
130
|
+
*/
|
|
131
|
+
whereRaw(expr: string, params: SQLQueryBindings[] = []): this {
|
|
132
|
+
return this.whereExpr(expr, params);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Add an IN clause for the given column with proper parameter binding.
|
|
137
|
+
* Replaces any existing conditions for the same column.
|
|
138
|
+
*
|
|
139
|
+
* @param column - Column name to check
|
|
140
|
+
* @param values - Non-empty array of values for the IN clause
|
|
141
|
+
* @returns this for method chaining
|
|
142
|
+
*/
|
|
143
|
+
whereIn(column: keyof T, values: SQLQueryBindings[]): this {
|
|
144
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
145
|
+
throw new Error("whereIn: values must be a non-empty array");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Remove any existing conditions for this column
|
|
149
|
+
this.removeExistingCondition(String(column), "IN");
|
|
150
|
+
|
|
151
|
+
const placeholders = values.map(() => "?").join(", ");
|
|
152
|
+
this.state.whereConditions.push(`${String(column)} IN (${placeholders})`);
|
|
153
|
+
this.state.whereParams.push(...values);
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Add a NOT IN clause for the given column with proper parameter binding.
|
|
159
|
+
* Replaces any existing conditions for the same column.
|
|
160
|
+
*
|
|
161
|
+
* @param column - Column name to check
|
|
162
|
+
* @param values - Non-empty array of values for the NOT IN clause
|
|
163
|
+
* @returns this for method chaining
|
|
164
|
+
*/
|
|
165
|
+
whereNotIn(column: keyof T, values: SQLQueryBindings[]): this {
|
|
166
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
167
|
+
throw new Error("whereNotIn: values must be a non-empty array");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Remove any existing conditions for this column
|
|
171
|
+
this.removeExistingCondition(String(column), "NOT IN");
|
|
172
|
+
|
|
173
|
+
const placeholders = values.map(() => "?").join(", ");
|
|
174
|
+
this.state.whereConditions.push(`${String(column)} NOT IN (${placeholders})`);
|
|
175
|
+
this.state.whereParams.push(...values);
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Add a comparison operator condition with proper null handling.
|
|
181
|
+
* Replaces any existing conditions for the same column.
|
|
182
|
+
* Supports: =, !=, <>, <, <=, >, >=, LIKE, GLOB, IS
|
|
183
|
+
*
|
|
184
|
+
* @param column - Column name
|
|
185
|
+
* @param op - Comparison operator
|
|
186
|
+
* @param value - Value to compare (handles null appropriately)
|
|
187
|
+
* @returns this for method chaining
|
|
188
|
+
*/
|
|
189
|
+
whereOp(column: keyof T, op: string, value: SQLQueryBindings): this {
|
|
190
|
+
const normalizedOp = (op ?? "").toUpperCase().trim();
|
|
191
|
+
const allowed = [
|
|
192
|
+
"=",
|
|
193
|
+
"!=",
|
|
194
|
+
"<>",
|
|
195
|
+
"<",
|
|
196
|
+
"<=",
|
|
197
|
+
">",
|
|
198
|
+
">=",
|
|
199
|
+
"LIKE",
|
|
200
|
+
"GLOB",
|
|
201
|
+
"IS",
|
|
202
|
+
"IS NOT",
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
if (!allowed.includes(normalizedOp)) {
|
|
206
|
+
throw new Error(`whereOp: operator "${op}" not supported`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Handle null special-casing for IS / IS NOT and equality operators
|
|
210
|
+
if (
|
|
211
|
+
(value === null || value === undefined) &&
|
|
212
|
+
(normalizedOp === "=" || normalizedOp === "IS")
|
|
213
|
+
) {
|
|
214
|
+
this.state.whereConditions.push(`${String(column)} IS NULL`);
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
(value === null || value === undefined) &&
|
|
220
|
+
(normalizedOp === "!=" ||
|
|
221
|
+
normalizedOp === "<>" ||
|
|
222
|
+
normalizedOp === "IS NOT")
|
|
223
|
+
) {
|
|
224
|
+
this.state.whereConditions.push(`${String(column)} IS NOT NULL`);
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Normal param-bound condition
|
|
229
|
+
this.state.whereConditions.push(`${String(column)} ${normalizedOp} ?`);
|
|
230
|
+
this.state.whereParams.push(value);
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Add a BETWEEN condition for the given column.
|
|
236
|
+
* Replaces any existing conditions for the same column.
|
|
237
|
+
*
|
|
238
|
+
* @param column - Column name
|
|
239
|
+
* @param min - Minimum value (inclusive)
|
|
240
|
+
* @param max - Maximum value (inclusive)
|
|
241
|
+
* @returns this for method chaining
|
|
242
|
+
*/
|
|
243
|
+
whereBetween(
|
|
244
|
+
column: keyof T,
|
|
245
|
+
min: SQLQueryBindings,
|
|
246
|
+
max: SQLQueryBindings,
|
|
247
|
+
): this {
|
|
248
|
+
// Remove any existing conditions for this column
|
|
249
|
+
this.removeExistingCondition(String(column), "BETWEEN");
|
|
250
|
+
|
|
251
|
+
this.state.whereConditions.push(`${String(column)} BETWEEN ? AND ?`);
|
|
252
|
+
this.state.whereParams.push(min, max);
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Add a NOT BETWEEN condition for the given column.
|
|
258
|
+
* Replaces any existing conditions for the same column.
|
|
259
|
+
*
|
|
260
|
+
* @param column - Column name
|
|
261
|
+
* @param min - Minimum value (exclusive)
|
|
262
|
+
* @param max - Maximum value (exclusive)
|
|
263
|
+
* @returns this for method chaining
|
|
264
|
+
*/
|
|
265
|
+
whereNotBetween(
|
|
266
|
+
column: keyof T,
|
|
267
|
+
min: SQLQueryBindings,
|
|
268
|
+
max: SQLQueryBindings,
|
|
269
|
+
): this {
|
|
270
|
+
// Remove any existing conditions for this column
|
|
271
|
+
this.removeExistingCondition(String(column), "NOT BETWEEN");
|
|
272
|
+
|
|
273
|
+
this.state.whereConditions.push(`${String(column)} NOT BETWEEN ? AND ?`);
|
|
274
|
+
this.state.whereParams.push(min, max);
|
|
275
|
+
return this;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Add an IS NULL condition for the given column.
|
|
280
|
+
* Replaces any existing conditions for the same column.
|
|
281
|
+
*
|
|
282
|
+
* @param column - Column name
|
|
283
|
+
* @returns this for method chaining
|
|
284
|
+
*/
|
|
285
|
+
whereNull(column: keyof T): this {
|
|
286
|
+
// Remove any existing conditions for this column
|
|
287
|
+
this.removeExistingCondition(String(column));
|
|
288
|
+
|
|
289
|
+
this.state.whereConditions.push(`${String(column)} IS NULL`);
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Add an IS NOT NULL condition for the given column.
|
|
295
|
+
* Replaces any existing conditions for the same column.
|
|
296
|
+
*
|
|
297
|
+
* @param column - Column name
|
|
298
|
+
* @returns this for method chaining
|
|
299
|
+
*/
|
|
300
|
+
whereNotNull(column: keyof T): this {
|
|
301
|
+
// Remove any existing conditions for this column
|
|
302
|
+
this.removeExistingCondition(String(column));
|
|
303
|
+
|
|
304
|
+
this.state.whereConditions.push(`${String(column)} IS NOT NULL`);
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
}
|