@qualithm/arrow-flight-sql-js 0.0.1 → 0.1.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.
Potentially problematic release.
This version of @qualithm/arrow-flight-sql-js might be problematic. Click here for more details.
- package/README.md +80 -0
- package/dist/arrow.js +1 -1
- package/dist/arrow.js.map +1 -1
- package/dist/client.d.ts +165 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +486 -46
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/metrics.d.ts +20 -16
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +12 -7
- package/dist/metrics.js.map +1 -1
- package/dist/pool.d.ts +1 -1
- package/dist/pool.d.ts.map +1 -1
- package/dist/pool.js +1 -1
- package/dist/pool.js.map +1 -1
- package/dist/proto.d.ts +2 -2
- package/dist/proto.d.ts.map +1 -1
- package/dist/proto.js +1 -1
- package/dist/proto.js.map +1 -1
- package/dist/query-builder.d.ts +362 -0
- package/dist/query-builder.d.ts.map +1 -1
- package/dist/query-builder.js +732 -2
- package/dist/query-builder.js.map +1 -1
- package/dist/retry.d.ts +2 -2
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +3 -3
- package/dist/retry.js.map +1 -1
- package/dist/types.d.ts +137 -58
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +25 -0
- package/dist/types.js.map +1 -1
- package/package.json +3 -2
- package/proto/Flight.proto +0 -645
- package/proto/FlightSql.proto +0 -1925
package/dist/query-builder.js
CHANGED
|
@@ -1,3 +1,733 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Fluent SQL query builder for constructing type-safe queries.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a query builder API that generates SQL strings
|
|
5
|
+
* compatible with the FlightSqlClient.query() and executeUpdate() methods.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { QueryBuilder } from "@qualithm/arrow-flight-sql-js"
|
|
10
|
+
*
|
|
11
|
+
* const query = new QueryBuilder()
|
|
12
|
+
* .select("id", "name", "email")
|
|
13
|
+
* .from("users")
|
|
14
|
+
* .where("status", "=", "active")
|
|
15
|
+
* .orderBy("created_at", "DESC")
|
|
16
|
+
* .limit(10)
|
|
17
|
+
* .build()
|
|
18
|
+
*
|
|
19
|
+
* const result = await client.query(query)
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Value Escaping
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* Creates a raw SQL expression that won't be escaped.
|
|
29
|
+
*
|
|
30
|
+
* WARNING: Only use with trusted input. Raw expressions bypass escaping
|
|
31
|
+
* and can lead to SQL injection if used with untrusted data.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const query = new QueryBuilder()
|
|
36
|
+
* .select("*")
|
|
37
|
+
* .from("events")
|
|
38
|
+
* .where("created_at", ">", raw("NOW() - INTERVAL '1 day'"))
|
|
39
|
+
* .build()
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function raw(sql) {
|
|
43
|
+
return { __raw: true, sql };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Checks if a value is a raw SQL expression.
|
|
47
|
+
*/
|
|
48
|
+
function isRaw(value) {
|
|
49
|
+
return typeof value === "object" && value !== null && "__raw" in value;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Escape a SQL identifier (table name, column name).
|
|
53
|
+
* Uses double quotes for standard SQL compliance.
|
|
54
|
+
*/
|
|
55
|
+
export function escapeIdentifier(identifier) {
|
|
56
|
+
// Handle qualified names (schema.table, table.column)
|
|
57
|
+
if (identifier.includes(".")) {
|
|
58
|
+
return identifier
|
|
59
|
+
.split(".")
|
|
60
|
+
.map((part) => escapeIdentifier(part))
|
|
61
|
+
.join(".");
|
|
62
|
+
}
|
|
63
|
+
// Handle wildcards
|
|
64
|
+
if (identifier === "*") {
|
|
65
|
+
return "*";
|
|
66
|
+
}
|
|
67
|
+
// Handle expressions with parentheses (function calls like COUNT(*))
|
|
68
|
+
if (identifier.includes("(") || identifier.includes(")")) {
|
|
69
|
+
return identifier;
|
|
70
|
+
}
|
|
71
|
+
// Escape by doubling quotes and wrapping
|
|
72
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Escape a SQL string value.
|
|
76
|
+
* Uses single quotes with proper escaping.
|
|
77
|
+
*/
|
|
78
|
+
export function escapeString(value) {
|
|
79
|
+
// Escape single quotes by doubling them
|
|
80
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Format a SQL value for inclusion in a query.
|
|
84
|
+
*/
|
|
85
|
+
export function formatValue(value) {
|
|
86
|
+
if (value === null) {
|
|
87
|
+
return "NULL";
|
|
88
|
+
}
|
|
89
|
+
if (isRaw(value)) {
|
|
90
|
+
return value.sql;
|
|
91
|
+
}
|
|
92
|
+
if (typeof value === "string") {
|
|
93
|
+
return escapeString(value);
|
|
94
|
+
}
|
|
95
|
+
if (typeof value === "number") {
|
|
96
|
+
if (!Number.isFinite(value)) {
|
|
97
|
+
throw new Error(`Invalid numeric value: ${String(value)}`);
|
|
98
|
+
}
|
|
99
|
+
return String(value);
|
|
100
|
+
}
|
|
101
|
+
if (typeof value === "bigint") {
|
|
102
|
+
return String(value);
|
|
103
|
+
}
|
|
104
|
+
if (typeof value === "boolean") {
|
|
105
|
+
return value ? "TRUE" : "FALSE";
|
|
106
|
+
}
|
|
107
|
+
if (value instanceof Date) {
|
|
108
|
+
return `TIMESTAMP '${value.toISOString()}'`;
|
|
109
|
+
}
|
|
110
|
+
if (Array.isArray(value)) {
|
|
111
|
+
const formatted = value.map(formatValue);
|
|
112
|
+
return `(${formatted.join(", ")})`;
|
|
113
|
+
}
|
|
114
|
+
throw new Error(`Unsupported value type: ${typeof value}`);
|
|
115
|
+
}
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// QueryBuilder Class
|
|
118
|
+
// ============================================================================
|
|
119
|
+
/**
|
|
120
|
+
* Fluent SQL query builder.
|
|
121
|
+
*
|
|
122
|
+
* Supports SELECT, INSERT, UPDATE, and DELETE operations with a chainable API.
|
|
123
|
+
*
|
|
124
|
+
* @example SELECT query
|
|
125
|
+
* ```typescript
|
|
126
|
+
* const sql = new QueryBuilder()
|
|
127
|
+
* .select("id", "name")
|
|
128
|
+
* .from("users")
|
|
129
|
+
* .where("active", "=", true)
|
|
130
|
+
* .limit(10)
|
|
131
|
+
* .build()
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* @example INSERT query
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const sql = new QueryBuilder()
|
|
137
|
+
* .insertInto("users")
|
|
138
|
+
* .columns("name", "email")
|
|
139
|
+
* .values("Alice", "alice@example.com")
|
|
140
|
+
* .build()
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @example UPDATE query
|
|
144
|
+
* ```typescript
|
|
145
|
+
* const sql = new QueryBuilder()
|
|
146
|
+
* .update("users")
|
|
147
|
+
* .set("status", "inactive")
|
|
148
|
+
* .where("last_login", "<", new Date("2024-01-01"))
|
|
149
|
+
* .build()
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* @example DELETE query
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const sql = new QueryBuilder()
|
|
155
|
+
* .deleteFrom("users")
|
|
156
|
+
* .where("status", "=", "deleted")
|
|
157
|
+
* .build()
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export class QueryBuilder {
|
|
161
|
+
_operation = "SELECT";
|
|
162
|
+
_distinct = false;
|
|
163
|
+
_columns = [];
|
|
164
|
+
_table = "";
|
|
165
|
+
_tableAlias;
|
|
166
|
+
_joins = [];
|
|
167
|
+
_conditions = [];
|
|
168
|
+
_groupBy = [];
|
|
169
|
+
_having = [];
|
|
170
|
+
_orderBy = [];
|
|
171
|
+
_limit;
|
|
172
|
+
_offset;
|
|
173
|
+
_parameterized = false;
|
|
174
|
+
_params = [];
|
|
175
|
+
// For INSERT
|
|
176
|
+
_insertColumns = [];
|
|
177
|
+
_insertValues = [];
|
|
178
|
+
// For UPDATE
|
|
179
|
+
_setValues = new Map();
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// SELECT Operations
|
|
182
|
+
// ============================================================================
|
|
183
|
+
/**
|
|
184
|
+
* Specify columns to select.
|
|
185
|
+
*
|
|
186
|
+
* @param columns - Column names or column specs with aliases
|
|
187
|
+
* @returns this for chaining
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* .select("id", "name")
|
|
192
|
+
* .select({ column: "email", alias: "user_email" })
|
|
193
|
+
* .select("*")
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
select(...columns) {
|
|
197
|
+
this._operation = "SELECT";
|
|
198
|
+
this._columns.push(...columns);
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Select distinct rows only.
|
|
203
|
+
*/
|
|
204
|
+
distinct() {
|
|
205
|
+
this._distinct = true;
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Specify the table to query from.
|
|
210
|
+
*
|
|
211
|
+
* @param table - Table name
|
|
212
|
+
* @param alias - Optional table alias
|
|
213
|
+
*/
|
|
214
|
+
from(table, alias) {
|
|
215
|
+
this._table = table;
|
|
216
|
+
this._tableAlias = alias;
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// JOIN Operations
|
|
221
|
+
// ============================================================================
|
|
222
|
+
/**
|
|
223
|
+
* Add a JOIN clause.
|
|
224
|
+
*
|
|
225
|
+
* @param type - Join type (INNER, LEFT, RIGHT, FULL, CROSS)
|
|
226
|
+
* @param table - Table to join
|
|
227
|
+
* @param on - Join condition
|
|
228
|
+
* @param alias - Optional table alias
|
|
229
|
+
*/
|
|
230
|
+
join(type, table, on, alias) {
|
|
231
|
+
this._joins.push({ type, table, alias, on });
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Add an INNER JOIN clause.
|
|
236
|
+
*/
|
|
237
|
+
innerJoin(table, on, alias) {
|
|
238
|
+
return this.join("INNER", table, on, alias);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Add a LEFT JOIN clause.
|
|
242
|
+
*/
|
|
243
|
+
leftJoin(table, on, alias) {
|
|
244
|
+
return this.join("LEFT", table, on, alias);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Add a RIGHT JOIN clause.
|
|
248
|
+
*/
|
|
249
|
+
rightJoin(table, on, alias) {
|
|
250
|
+
return this.join("RIGHT", table, on, alias);
|
|
251
|
+
}
|
|
252
|
+
// ============================================================================
|
|
253
|
+
// WHERE Operations
|
|
254
|
+
// ============================================================================
|
|
255
|
+
/**
|
|
256
|
+
* Add a WHERE condition.
|
|
257
|
+
*
|
|
258
|
+
* @param column - Column name
|
|
259
|
+
* @param operator - Comparison operator
|
|
260
|
+
* @param value - Value to compare against
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```typescript
|
|
264
|
+
* .where("status", "=", "active")
|
|
265
|
+
* .where("age", ">=", 18)
|
|
266
|
+
* .where("role", "IN", ["admin", "moderator"])
|
|
267
|
+
* .where("deleted_at", "IS", null)
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
where(column, operator, value) {
|
|
271
|
+
this._conditions.push({
|
|
272
|
+
column,
|
|
273
|
+
operator,
|
|
274
|
+
value,
|
|
275
|
+
logical: "AND"
|
|
276
|
+
});
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Add an OR WHERE condition.
|
|
281
|
+
*/
|
|
282
|
+
orWhere(column, operator, value) {
|
|
283
|
+
this._conditions.push({
|
|
284
|
+
column,
|
|
285
|
+
operator,
|
|
286
|
+
value,
|
|
287
|
+
logical: "OR"
|
|
288
|
+
});
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Add a WHERE BETWEEN condition.
|
|
293
|
+
*/
|
|
294
|
+
whereBetween(column, min, max) {
|
|
295
|
+
// Use raw expression for BETWEEN
|
|
296
|
+
this._conditions.push({
|
|
297
|
+
column,
|
|
298
|
+
operator: "BETWEEN",
|
|
299
|
+
value: raw(`${formatValue(min)} AND ${formatValue(max)}`),
|
|
300
|
+
logical: "AND"
|
|
301
|
+
});
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Add a WHERE IN condition.
|
|
306
|
+
*/
|
|
307
|
+
whereIn(column, values) {
|
|
308
|
+
return this.where(column, "IN", values);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Add a WHERE IS NULL condition.
|
|
312
|
+
*/
|
|
313
|
+
whereNull(column) {
|
|
314
|
+
return this.where(column, "IS", null);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Add a WHERE IS NOT NULL condition.
|
|
318
|
+
*/
|
|
319
|
+
whereNotNull(column) {
|
|
320
|
+
return this.where(column, "IS NOT", null);
|
|
321
|
+
}
|
|
322
|
+
// ============================================================================
|
|
323
|
+
// GROUP BY / HAVING
|
|
324
|
+
// ============================================================================
|
|
325
|
+
/**
|
|
326
|
+
* Add GROUP BY columns.
|
|
327
|
+
*/
|
|
328
|
+
groupBy(...columns) {
|
|
329
|
+
this._groupBy.push(...columns);
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Add a HAVING condition.
|
|
334
|
+
*/
|
|
335
|
+
having(column, operator, value) {
|
|
336
|
+
this._having.push({
|
|
337
|
+
column,
|
|
338
|
+
operator,
|
|
339
|
+
value,
|
|
340
|
+
logical: "AND"
|
|
341
|
+
});
|
|
342
|
+
return this;
|
|
343
|
+
}
|
|
344
|
+
// ============================================================================
|
|
345
|
+
// ORDER BY / LIMIT / OFFSET
|
|
346
|
+
// ============================================================================
|
|
347
|
+
/**
|
|
348
|
+
* Add ORDER BY clause.
|
|
349
|
+
*
|
|
350
|
+
* @param column - Column to order by
|
|
351
|
+
* @param direction - Sort direction (ASC or DESC)
|
|
352
|
+
* @param nulls - NULLS FIRST or NULLS LAST
|
|
353
|
+
*/
|
|
354
|
+
orderBy(column, direction = "ASC", nulls) {
|
|
355
|
+
this._orderBy.push({ column, direction, nulls });
|
|
356
|
+
return this;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Set the maximum number of rows to return.
|
|
360
|
+
*/
|
|
361
|
+
limit(count) {
|
|
362
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
363
|
+
throw new Error("LIMIT must be a non-negative integer");
|
|
364
|
+
}
|
|
365
|
+
this._limit = count;
|
|
366
|
+
return this;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Set the number of rows to skip.
|
|
370
|
+
*/
|
|
371
|
+
offset(count) {
|
|
372
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
373
|
+
throw new Error("OFFSET must be a non-negative integer");
|
|
374
|
+
}
|
|
375
|
+
this._offset = count;
|
|
376
|
+
return this;
|
|
377
|
+
}
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// INSERT Operations
|
|
380
|
+
// ============================================================================
|
|
381
|
+
/**
|
|
382
|
+
* Start an INSERT statement.
|
|
383
|
+
*
|
|
384
|
+
* @param table - Table to insert into
|
|
385
|
+
*/
|
|
386
|
+
insertInto(table) {
|
|
387
|
+
this._operation = "INSERT";
|
|
388
|
+
this._table = table;
|
|
389
|
+
return this;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Specify columns for INSERT.
|
|
393
|
+
*/
|
|
394
|
+
columns(...columns) {
|
|
395
|
+
this._insertColumns = columns;
|
|
396
|
+
return this;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Add values to INSERT.
|
|
400
|
+
* Can be called multiple times for multi-row inserts.
|
|
401
|
+
*
|
|
402
|
+
* @param values - Values corresponding to columns
|
|
403
|
+
*/
|
|
404
|
+
values(...values) {
|
|
405
|
+
this._insertValues.push(values);
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
// ============================================================================
|
|
409
|
+
// UPDATE Operations
|
|
410
|
+
// ============================================================================
|
|
411
|
+
/**
|
|
412
|
+
* Start an UPDATE statement.
|
|
413
|
+
*
|
|
414
|
+
* @param table - Table to update
|
|
415
|
+
*/
|
|
416
|
+
update(table) {
|
|
417
|
+
this._operation = "UPDATE";
|
|
418
|
+
this._table = table;
|
|
419
|
+
return this;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Set a column value for UPDATE.
|
|
423
|
+
*
|
|
424
|
+
* @param column - Column to update
|
|
425
|
+
* @param value - New value
|
|
426
|
+
*/
|
|
427
|
+
set(column, value) {
|
|
428
|
+
this._setValues.set(column, value);
|
|
429
|
+
return this;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Set multiple column values for UPDATE.
|
|
433
|
+
*
|
|
434
|
+
* @param values - Object mapping column names to values
|
|
435
|
+
*/
|
|
436
|
+
setMany(values) {
|
|
437
|
+
for (const [column, value] of Object.entries(values)) {
|
|
438
|
+
this._setValues.set(column, value);
|
|
439
|
+
}
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
442
|
+
// ============================================================================
|
|
443
|
+
// DELETE Operations
|
|
444
|
+
// ============================================================================
|
|
445
|
+
/**
|
|
446
|
+
* Start a DELETE statement.
|
|
447
|
+
*
|
|
448
|
+
* @param table - Table to delete from
|
|
449
|
+
*/
|
|
450
|
+
deleteFrom(table) {
|
|
451
|
+
this._operation = "DELETE";
|
|
452
|
+
this._table = table;
|
|
453
|
+
return this;
|
|
454
|
+
}
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// Build Methods
|
|
457
|
+
// ============================================================================
|
|
458
|
+
/**
|
|
459
|
+
* Build the SQL query string.
|
|
460
|
+
*
|
|
461
|
+
* @returns The complete SQL query string
|
|
462
|
+
*/
|
|
463
|
+
build() {
|
|
464
|
+
switch (this._operation) {
|
|
465
|
+
case "SELECT":
|
|
466
|
+
return this.buildSelect();
|
|
467
|
+
case "INSERT":
|
|
468
|
+
return this.buildInsert();
|
|
469
|
+
case "UPDATE":
|
|
470
|
+
return this.buildUpdate();
|
|
471
|
+
case "DELETE":
|
|
472
|
+
return this.buildDelete();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Build query with parameter placeholders for prepared statements.
|
|
477
|
+
*
|
|
478
|
+
* @returns Object containing SQL with placeholders and parameter values
|
|
479
|
+
*/
|
|
480
|
+
buildParameterized() {
|
|
481
|
+
this._parameterized = true;
|
|
482
|
+
this._params = [];
|
|
483
|
+
const sql = this.build();
|
|
484
|
+
return {
|
|
485
|
+
sql,
|
|
486
|
+
params: this._params
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Create a copy of this query builder.
|
|
491
|
+
*/
|
|
492
|
+
clone() {
|
|
493
|
+
const cloned = new QueryBuilder();
|
|
494
|
+
cloned._operation = this._operation;
|
|
495
|
+
cloned._distinct = this._distinct;
|
|
496
|
+
cloned._columns = [...this._columns];
|
|
497
|
+
cloned._table = this._table;
|
|
498
|
+
cloned._tableAlias = this._tableAlias;
|
|
499
|
+
cloned._joins = [...this._joins];
|
|
500
|
+
cloned._conditions = [...this._conditions];
|
|
501
|
+
cloned._groupBy = [...this._groupBy];
|
|
502
|
+
cloned._having = [...this._having];
|
|
503
|
+
cloned._orderBy = [...this._orderBy];
|
|
504
|
+
cloned._limit = this._limit;
|
|
505
|
+
cloned._offset = this._offset;
|
|
506
|
+
cloned._insertColumns = [...this._insertColumns];
|
|
507
|
+
cloned._insertValues = this._insertValues.map((v) => [...v]);
|
|
508
|
+
cloned._setValues = new Map(this._setValues);
|
|
509
|
+
return cloned;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Reset the builder to initial state.
|
|
513
|
+
*/
|
|
514
|
+
reset() {
|
|
515
|
+
this._operation = "SELECT";
|
|
516
|
+
this._distinct = false;
|
|
517
|
+
this._columns = [];
|
|
518
|
+
this._table = "";
|
|
519
|
+
this._tableAlias = undefined;
|
|
520
|
+
this._joins = [];
|
|
521
|
+
this._conditions = [];
|
|
522
|
+
this._groupBy = [];
|
|
523
|
+
this._having = [];
|
|
524
|
+
this._orderBy = [];
|
|
525
|
+
this._limit = undefined;
|
|
526
|
+
this._offset = undefined;
|
|
527
|
+
this._parameterized = false;
|
|
528
|
+
this._params = [];
|
|
529
|
+
this._insertColumns = [];
|
|
530
|
+
this._insertValues = [];
|
|
531
|
+
this._setValues.clear();
|
|
532
|
+
return this;
|
|
533
|
+
}
|
|
534
|
+
// ============================================================================
|
|
535
|
+
// Private Build Methods
|
|
536
|
+
// ============================================================================
|
|
537
|
+
buildSelect() {
|
|
538
|
+
const parts = [];
|
|
539
|
+
// SELECT
|
|
540
|
+
parts.push("SELECT");
|
|
541
|
+
if (this._distinct) {
|
|
542
|
+
parts.push("DISTINCT");
|
|
543
|
+
}
|
|
544
|
+
// Columns
|
|
545
|
+
const columns = this._columns.length === 0
|
|
546
|
+
? "*"
|
|
547
|
+
: this._columns
|
|
548
|
+
.map((col) => {
|
|
549
|
+
if (typeof col === "string") {
|
|
550
|
+
return escapeIdentifier(col);
|
|
551
|
+
}
|
|
552
|
+
return `${escapeIdentifier(col.column)} AS ${escapeIdentifier(col.alias)}`;
|
|
553
|
+
})
|
|
554
|
+
.join(", ");
|
|
555
|
+
parts.push(columns);
|
|
556
|
+
// FROM
|
|
557
|
+
if (this._table !== "") {
|
|
558
|
+
parts.push("FROM");
|
|
559
|
+
let tableRef = escapeIdentifier(this._table);
|
|
560
|
+
if (this._tableAlias !== undefined && this._tableAlias !== "") {
|
|
561
|
+
tableRef += ` AS ${escapeIdentifier(this._tableAlias)}`;
|
|
562
|
+
}
|
|
563
|
+
parts.push(tableRef);
|
|
564
|
+
}
|
|
565
|
+
// JOINs
|
|
566
|
+
for (const join of this._joins) {
|
|
567
|
+
let joinClause = `${join.type} JOIN ${escapeIdentifier(join.table)}`;
|
|
568
|
+
if (join.alias !== undefined && join.alias !== "") {
|
|
569
|
+
joinClause += ` AS ${escapeIdentifier(join.alias)}`;
|
|
570
|
+
}
|
|
571
|
+
if (join.type !== "CROSS") {
|
|
572
|
+
joinClause += ` ON ${join.on}`;
|
|
573
|
+
}
|
|
574
|
+
parts.push(joinClause);
|
|
575
|
+
}
|
|
576
|
+
// WHERE
|
|
577
|
+
if (this._conditions.length > 0) {
|
|
578
|
+
parts.push("WHERE");
|
|
579
|
+
parts.push(this.buildConditions(this._conditions));
|
|
580
|
+
}
|
|
581
|
+
// GROUP BY
|
|
582
|
+
if (this._groupBy.length > 0) {
|
|
583
|
+
parts.push("GROUP BY");
|
|
584
|
+
parts.push(this._groupBy.map(escapeIdentifier).join(", "));
|
|
585
|
+
}
|
|
586
|
+
// HAVING
|
|
587
|
+
if (this._having.length > 0) {
|
|
588
|
+
parts.push("HAVING");
|
|
589
|
+
parts.push(this.buildConditions(this._having));
|
|
590
|
+
}
|
|
591
|
+
// ORDER BY
|
|
592
|
+
if (this._orderBy.length > 0) {
|
|
593
|
+
parts.push("ORDER BY");
|
|
594
|
+
parts.push(this._orderBy
|
|
595
|
+
.map((spec) => {
|
|
596
|
+
let clause = `${escapeIdentifier(spec.column)} ${spec.direction}`;
|
|
597
|
+
if (spec.nulls) {
|
|
598
|
+
clause += ` NULLS ${spec.nulls}`;
|
|
599
|
+
}
|
|
600
|
+
return clause;
|
|
601
|
+
})
|
|
602
|
+
.join(", "));
|
|
603
|
+
}
|
|
604
|
+
// LIMIT
|
|
605
|
+
if (this._limit !== undefined) {
|
|
606
|
+
parts.push(`LIMIT ${String(this._limit)}`);
|
|
607
|
+
}
|
|
608
|
+
// OFFSET
|
|
609
|
+
if (this._offset !== undefined) {
|
|
610
|
+
parts.push(`OFFSET ${String(this._offset)}`);
|
|
611
|
+
}
|
|
612
|
+
return parts.join(" ");
|
|
613
|
+
}
|
|
614
|
+
buildInsert() {
|
|
615
|
+
if (!this._table) {
|
|
616
|
+
throw new Error("INSERT requires a table name");
|
|
617
|
+
}
|
|
618
|
+
if (this._insertColumns.length === 0) {
|
|
619
|
+
throw new Error("INSERT requires columns");
|
|
620
|
+
}
|
|
621
|
+
if (this._insertValues.length === 0) {
|
|
622
|
+
throw new Error("INSERT requires values");
|
|
623
|
+
}
|
|
624
|
+
const parts = [];
|
|
625
|
+
// INSERT INTO
|
|
626
|
+
parts.push(`INSERT INTO ${escapeIdentifier(this._table)}`);
|
|
627
|
+
// Columns
|
|
628
|
+
parts.push(`(${this._insertColumns.map(escapeIdentifier).join(", ")})`);
|
|
629
|
+
// VALUES
|
|
630
|
+
parts.push("VALUES");
|
|
631
|
+
const valueRows = this._insertValues.map((row) => {
|
|
632
|
+
const formattedValues = row.map((v) => this.formatOrParam(v));
|
|
633
|
+
return `(${formattedValues.join(", ")})`;
|
|
634
|
+
});
|
|
635
|
+
parts.push(valueRows.join(", "));
|
|
636
|
+
return parts.join(" ");
|
|
637
|
+
}
|
|
638
|
+
buildUpdate() {
|
|
639
|
+
if (!this._table) {
|
|
640
|
+
throw new Error("UPDATE requires a table name");
|
|
641
|
+
}
|
|
642
|
+
if (this._setValues.size === 0) {
|
|
643
|
+
throw new Error("UPDATE requires SET values");
|
|
644
|
+
}
|
|
645
|
+
const parts = [];
|
|
646
|
+
// UPDATE
|
|
647
|
+
parts.push(`UPDATE ${escapeIdentifier(this._table)}`);
|
|
648
|
+
// SET
|
|
649
|
+
parts.push("SET");
|
|
650
|
+
const setClauses = [];
|
|
651
|
+
for (const [column, value] of this._setValues) {
|
|
652
|
+
setClauses.push(`${escapeIdentifier(column)} = ${this.formatOrParam(value)}`);
|
|
653
|
+
}
|
|
654
|
+
parts.push(setClauses.join(", "));
|
|
655
|
+
// WHERE
|
|
656
|
+
if (this._conditions.length > 0) {
|
|
657
|
+
parts.push("WHERE");
|
|
658
|
+
parts.push(this.buildConditions(this._conditions));
|
|
659
|
+
}
|
|
660
|
+
return parts.join(" ");
|
|
661
|
+
}
|
|
662
|
+
buildDelete() {
|
|
663
|
+
if (!this._table) {
|
|
664
|
+
throw new Error("DELETE requires a table name");
|
|
665
|
+
}
|
|
666
|
+
const parts = [];
|
|
667
|
+
// DELETE FROM
|
|
668
|
+
parts.push(`DELETE FROM ${escapeIdentifier(this._table)}`);
|
|
669
|
+
// WHERE
|
|
670
|
+
if (this._conditions.length > 0) {
|
|
671
|
+
parts.push("WHERE");
|
|
672
|
+
parts.push(this.buildConditions(this._conditions));
|
|
673
|
+
}
|
|
674
|
+
return parts.join(" ");
|
|
675
|
+
}
|
|
676
|
+
buildConditions(conditions) {
|
|
677
|
+
return conditions
|
|
678
|
+
.map((cond, index) => {
|
|
679
|
+
const prefix = index === 0 ? "" : `${cond.logical} `;
|
|
680
|
+
const column = escapeIdentifier(cond.column);
|
|
681
|
+
const value = this.formatOrParam(cond.value);
|
|
682
|
+
return `${prefix}${column} ${cond.operator} ${value}`;
|
|
683
|
+
})
|
|
684
|
+
.join(" ");
|
|
685
|
+
}
|
|
686
|
+
formatOrParam(value) {
|
|
687
|
+
if (this._parameterized && !isRaw(value)) {
|
|
688
|
+
this._params.push(value);
|
|
689
|
+
return `$${String(this._params.length)}`;
|
|
690
|
+
}
|
|
691
|
+
return formatValue(value);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// ============================================================================
|
|
695
|
+
// Convenience Functions
|
|
696
|
+
// ============================================================================
|
|
697
|
+
/**
|
|
698
|
+
* Create a new QueryBuilder for a SELECT query.
|
|
699
|
+
*
|
|
700
|
+
* @param columns - Columns to select
|
|
701
|
+
* @returns A new QueryBuilder with SELECT initialized
|
|
702
|
+
*/
|
|
703
|
+
export function select(...columns) {
|
|
704
|
+
return new QueryBuilder().select(...columns);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Create a new QueryBuilder for an INSERT query.
|
|
708
|
+
*
|
|
709
|
+
* @param table - Table to insert into
|
|
710
|
+
* @returns A new QueryBuilder with INSERT initialized
|
|
711
|
+
*/
|
|
712
|
+
export function insertInto(table) {
|
|
713
|
+
return new QueryBuilder().insertInto(table);
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Create a new QueryBuilder for an UPDATE query.
|
|
717
|
+
*
|
|
718
|
+
* @param table - Table to update
|
|
719
|
+
* @returns A new QueryBuilder with UPDATE initialized
|
|
720
|
+
*/
|
|
721
|
+
export function update(table) {
|
|
722
|
+
return new QueryBuilder().update(table);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Create a new QueryBuilder for a DELETE query.
|
|
726
|
+
*
|
|
727
|
+
* @param table - Table to delete from
|
|
728
|
+
* @returns A new QueryBuilder with DELETE initialized
|
|
729
|
+
*/
|
|
730
|
+
export function deleteFrom(table) {
|
|
731
|
+
return new QueryBuilder().deleteFrom(table);
|
|
732
|
+
}
|
|
3
733
|
//# sourceMappingURL=query-builder.js.map
|