bunql 1.0.1-dev.4 → 1.0.1-dev.5
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/README.md +91 -3
- package/package.json +1 -1
- package/src/index.test.ts +4 -0
- package/src/index.ts +408 -171
- package/src/schema-new.ts +563 -0
- package/src/schema.test.ts +2 -3
- package/src/schema.ts +214 -618
package/src/index.ts
CHANGED
|
@@ -26,75 +26,235 @@ async function executeSql(executor: any, query: string, params?: any[]): Promise
|
|
|
26
26
|
return await executor(strings);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
throw new Error(`Parameter count mismatch: query has ${parts.length - 1} placeholders but ${params.length} parameters provided`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Create a proper template strings array
|
|
36
|
-
const strings: any = [...parts];
|
|
37
|
-
strings.raw = [...parts];
|
|
29
|
+
// Check if query uses PostgreSQL-style parameters ($1, $2, etc.)
|
|
30
|
+
const postgresPattern = /\$\d+/g;
|
|
31
|
+
const postgresMatches = query.match(postgresPattern);
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
if (postgresMatches && postgresMatches.length > 0) {
|
|
34
|
+
// Handle PostgreSQL-style parameters
|
|
35
|
+
if (postgresMatches.length !== params.length) {
|
|
36
|
+
throw new Error(`Parameter count mismatch: query has ${postgresMatches.length} placeholders but ${params.length} parameters provided`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Replace PostgreSQL parameters with ? placeholders
|
|
40
|
+
let processedQuery = query;
|
|
41
|
+
for (let i = 0; i < postgresMatches.length; i++) {
|
|
42
|
+
processedQuery = processedQuery.replace(`$${i + 1}`, '?');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Build the template literal with parameters
|
|
46
|
+
const parts = processedQuery.split('?');
|
|
47
|
+
const strings: any = [...parts];
|
|
48
|
+
strings.raw = [...parts];
|
|
49
|
+
|
|
50
|
+
// Call the executor as a tagged template
|
|
51
|
+
return await executor(strings, ...params);
|
|
52
|
+
} else {
|
|
53
|
+
// Handle SQLite-style parameters (?)
|
|
54
|
+
const parts = query.split('?');
|
|
55
|
+
if (parts.length - 1 !== params.length) {
|
|
56
|
+
throw new Error(`Parameter count mismatch: query has ${parts.length - 1} placeholders but ${params.length} parameters provided`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Create a proper template strings array
|
|
60
|
+
const strings: any = [...parts];
|
|
61
|
+
strings.raw = [...parts];
|
|
62
|
+
|
|
63
|
+
// Call the executor as a tagged template
|
|
64
|
+
return await executor(strings, ...params);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Connection pool for managing database connections
|
|
69
|
+
class ConnectionPool {
|
|
70
|
+
private static instance: ConnectionPool;
|
|
71
|
+
private connections: Map<string, SQL> = new Map();
|
|
72
|
+
private maxConnections = 10;
|
|
73
|
+
|
|
74
|
+
static getInstance(): ConnectionPool {
|
|
75
|
+
if (!ConnectionPool.instance) {
|
|
76
|
+
ConnectionPool.instance = new ConnectionPool();
|
|
77
|
+
}
|
|
78
|
+
return ConnectionPool.instance;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getConnection(connectionString: string): SQL {
|
|
82
|
+
if (!this.connections.has(connectionString)) {
|
|
83
|
+
if (this.connections.size >= this.maxConnections) {
|
|
84
|
+
// If pool is full, return a new connection (will be closed after use)
|
|
85
|
+
return new SQL(connectionString);
|
|
86
|
+
}
|
|
87
|
+
this.connections.set(connectionString, new SQL(connectionString));
|
|
88
|
+
}
|
|
89
|
+
return this.connections.get(connectionString)!;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
releaseConnection(connectionString: string, sql: SQL): void {
|
|
93
|
+
// Keep the connection in the pool for reuse
|
|
94
|
+
if (this.connections.size < this.maxConnections) {
|
|
95
|
+
this.connections.set(connectionString, sql);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
closeAll(): void {
|
|
100
|
+
for (const [key, sql] of this.connections) {
|
|
101
|
+
if (sql && typeof sql.close === 'function') {
|
|
102
|
+
sql.close();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.connections.clear();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Base class for query builders with auto-close support
|
|
110
|
+
abstract class BaseQuery {
|
|
111
|
+
protected sql: SQL;
|
|
112
|
+
protected transaction?: any;
|
|
113
|
+
protected bunql?: BunQL; // Reference to parent BunQL instance
|
|
114
|
+
|
|
115
|
+
constructor(sql: SQL, transaction?: any, bunql?: BunQL) {
|
|
116
|
+
this.sql = sql;
|
|
117
|
+
this.transaction = transaction;
|
|
118
|
+
this.bunql = bunql;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected async executeWithAutoClose<T>(operation: () => Promise<T>): Promise<T> {
|
|
122
|
+
if (this.bunql) {
|
|
123
|
+
await this.bunql['ensureConnection']();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const result = await operation();
|
|
128
|
+
|
|
129
|
+
// Only auto-close if not in transaction AND not using connection pool
|
|
130
|
+
if (!this.transaction && this.bunql && !this.bunql['connectionString']) {
|
|
131
|
+
await this.bunql.close();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// Only auto-close on error if not in transaction AND not using connection pool
|
|
137
|
+
if (!this.transaction && this.bunql && !this.bunql['connectionString']) {
|
|
138
|
+
await this.bunql.close();
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
41
143
|
}
|
|
42
144
|
|
|
43
145
|
export class BunQL {
|
|
44
146
|
private sql: SQL;
|
|
45
147
|
private _transaction?: any;
|
|
148
|
+
private connectionString?: string;
|
|
149
|
+
private isClosed: boolean = false;
|
|
150
|
+
private pool: ConnectionPool;
|
|
46
151
|
|
|
47
152
|
constructor(connectionString?: string) {
|
|
48
|
-
this.
|
|
153
|
+
this.connectionString = connectionString;
|
|
154
|
+
this.pool = ConnectionPool.getInstance();
|
|
155
|
+
|
|
156
|
+
if (connectionString) {
|
|
157
|
+
this.sql = this.pool.getConnection(connectionString);
|
|
158
|
+
} else {
|
|
159
|
+
this.sql = sql;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async ensureConnection(): Promise<void> {
|
|
164
|
+
if (this.isClosed && this.connectionString) {
|
|
165
|
+
this.sql = this.pool.getConnection(this.connectionString);
|
|
166
|
+
this.isClosed = false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async executeWithAutoClose<T>(operation: () => Promise<T>): Promise<T> {
|
|
171
|
+
await this.ensureConnection();
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const result = await operation();
|
|
175
|
+
|
|
176
|
+
// Only auto-close if not in transaction AND not using connection pool
|
|
177
|
+
if (!this._transaction && !this.connectionString) {
|
|
178
|
+
await this.close();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return result;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
// Only auto-close on error if not in transaction AND not using connection pool
|
|
184
|
+
if (!this._transaction && !this.connectionString) {
|
|
185
|
+
await this.close();
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
49
189
|
}
|
|
50
190
|
|
|
51
191
|
select<T = any>(columns?: string | string[]): SelectQuery<T> {
|
|
52
|
-
return new SelectQuery<T>(this.sql, this._transaction, columns);
|
|
192
|
+
return new SelectQuery<T>(this.sql, this._transaction, columns, this);
|
|
53
193
|
}
|
|
54
194
|
|
|
55
195
|
update(table: string): UpdateQuery {
|
|
56
|
-
return new UpdateQuery(this.sql, table, this._transaction);
|
|
196
|
+
return new UpdateQuery(this.sql, table, this._transaction, this);
|
|
57
197
|
}
|
|
58
198
|
|
|
59
199
|
insert(table: string): InsertQuery {
|
|
60
|
-
return new InsertQuery(this.sql, table, this._transaction);
|
|
200
|
+
return new InsertQuery(this.sql, table, this._transaction, this);
|
|
61
201
|
}
|
|
62
202
|
|
|
63
203
|
delete(table: string): DeleteQuery {
|
|
64
|
-
return new DeleteQuery(this.sql, table, this._transaction);
|
|
204
|
+
return new DeleteQuery(this.sql, table, this._transaction, this);
|
|
65
205
|
}
|
|
66
206
|
|
|
67
207
|
async run(query: string, params?: any[]): Promise<any> {
|
|
68
|
-
|
|
69
|
-
|
|
208
|
+
return await this.executeWithAutoClose(async () => {
|
|
209
|
+
const executor = this._transaction || this.sql;
|
|
210
|
+
return await executeSql(executor, query, params);
|
|
211
|
+
});
|
|
70
212
|
}
|
|
71
213
|
|
|
72
214
|
async all<T = any>(query: string, params?: any[]): Promise<T[]> {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
215
|
+
return await this.executeWithAutoClose(async () => {
|
|
216
|
+
const executor = this._transaction || this.sql;
|
|
217
|
+
const result = await executeSql(executor, query, params);
|
|
218
|
+
return Array.isArray(result) ? result : [];
|
|
219
|
+
});
|
|
76
220
|
}
|
|
77
221
|
|
|
78
222
|
async get<T = any>(query: string, params?: any[]): Promise<T | null> {
|
|
79
|
-
|
|
80
|
-
|
|
223
|
+
return await this.executeWithAutoClose(async () => {
|
|
224
|
+
const results = await this.all<T>(query, params);
|
|
225
|
+
return results.length > 0 ? results[0]! : null;
|
|
226
|
+
});
|
|
81
227
|
}
|
|
82
228
|
|
|
83
|
-
async begin<T>(callback: (tx:
|
|
229
|
+
async begin<T>(callback: (tx: BunQL) => Promise<T>): Promise<T> {
|
|
84
230
|
return await this.sql.begin(async (tx) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
231
|
+
// Create a transaction builder that reuses the existing connection
|
|
232
|
+
// Don't create a new BunQL instance, just modify the current one temporarily
|
|
233
|
+
const originalTransaction = this._transaction;
|
|
234
|
+
this._transaction = tx;
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
return await callback(this);
|
|
238
|
+
} finally {
|
|
239
|
+
// Restore original transaction state
|
|
240
|
+
this._transaction = originalTransaction;
|
|
241
|
+
}
|
|
89
242
|
});
|
|
90
243
|
}
|
|
91
244
|
|
|
92
245
|
async transaction<T>(callback: (trx: BunQL) => Promise<T>): Promise<T> {
|
|
93
246
|
return await this.sql.begin(async (tx) => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
247
|
+
// Create a transaction builder that reuses the existing connection
|
|
248
|
+
// Don't create a new BunQL instance, just modify the current one temporarily
|
|
249
|
+
const originalTransaction = this._transaction;
|
|
250
|
+
this._transaction = tx;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
return await callback(this);
|
|
254
|
+
} finally {
|
|
255
|
+
// Restore original transaction state
|
|
256
|
+
this._transaction = originalTransaction;
|
|
257
|
+
}
|
|
98
258
|
});
|
|
99
259
|
}
|
|
100
260
|
|
|
@@ -105,11 +265,19 @@ export class BunQL {
|
|
|
105
265
|
async rollback(): Promise<void> {
|
|
106
266
|
// Transactions are handled by Bun's SQL implementation
|
|
107
267
|
}
|
|
268
|
+
|
|
269
|
+
async close(): Promise<void> {
|
|
270
|
+
if (this.connectionString) {
|
|
271
|
+
// Release connection back to pool instead of closing it
|
|
272
|
+
this.pool.releaseConnection(this.connectionString, this.sql);
|
|
273
|
+
} else if (this.sql && typeof this.sql.close === 'function') {
|
|
274
|
+
await this.sql.close();
|
|
275
|
+
}
|
|
276
|
+
this.isClosed = true;
|
|
277
|
+
}
|
|
108
278
|
}
|
|
109
279
|
|
|
110
|
-
export class SelectQuery<T = any> {
|
|
111
|
-
private sql: SQL;
|
|
112
|
-
private transaction?: any;
|
|
280
|
+
export class SelectQuery<T = any> extends BaseQuery {
|
|
113
281
|
private columns: string;
|
|
114
282
|
private table?: string;
|
|
115
283
|
private whereClauses: string[] = [];
|
|
@@ -118,9 +286,8 @@ export class SelectQuery<T = any> {
|
|
|
118
286
|
private limitCount?: number;
|
|
119
287
|
private offsetCount?: number;
|
|
120
288
|
|
|
121
|
-
constructor(sql: SQL, transaction?: any, columns?: string | string[]) {
|
|
122
|
-
|
|
123
|
-
this.transaction = transaction;
|
|
289
|
+
constructor(sql: SQL, transaction?: any, columns?: string | string[], bunql?: BunQL) {
|
|
290
|
+
super(sql, transaction, bunql);
|
|
124
291
|
this.columns = this.formatColumns(columns);
|
|
125
292
|
}
|
|
126
293
|
|
|
@@ -136,27 +303,37 @@ export class SelectQuery<T = any> {
|
|
|
136
303
|
}
|
|
137
304
|
|
|
138
305
|
where(column: string, operator: string, value: any): this {
|
|
139
|
-
|
|
140
|
-
|
|
306
|
+
if (operator.toUpperCase() === 'IN' && Array.isArray(value)) {
|
|
307
|
+
const placeholders = value.map(() => '?').join(', ');
|
|
308
|
+
this.whereClauses.push(`"${column}" IN (${placeholders})`);
|
|
309
|
+
this.whereParams.push(...value);
|
|
310
|
+
} else if (operator.toUpperCase() === 'NOT IN' && Array.isArray(value)) {
|
|
311
|
+
const placeholders = value.map(() => '?').join(', ');
|
|
312
|
+
this.whereClauses.push(`"${column}" NOT IN (${placeholders})`);
|
|
313
|
+
this.whereParams.push(...value);
|
|
314
|
+
} else {
|
|
315
|
+
this.whereClauses.push(`"${column}" ${operator} ?`);
|
|
316
|
+
this.whereParams.push(value);
|
|
317
|
+
}
|
|
141
318
|
return this;
|
|
142
319
|
}
|
|
143
320
|
|
|
144
321
|
whereIn(column: string, values: any[]): this {
|
|
145
322
|
const placeholders = values.map(() => '?').join(', ');
|
|
146
|
-
this.whereClauses.push(
|
|
323
|
+
this.whereClauses.push(`"${column}" IN (${placeholders})`);
|
|
147
324
|
this.whereParams.push(...values);
|
|
148
325
|
return this;
|
|
149
326
|
}
|
|
150
327
|
|
|
151
328
|
whereNotIn(column: string, values: any[]): this {
|
|
152
329
|
const placeholders = values.map(() => '?').join(', ');
|
|
153
|
-
this.whereClauses.push(
|
|
330
|
+
this.whereClauses.push(`"${column}" NOT IN (${placeholders})`);
|
|
154
331
|
this.whereParams.push(...values);
|
|
155
332
|
return this;
|
|
156
333
|
}
|
|
157
334
|
|
|
158
335
|
orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
|
|
159
|
-
this.orderByClause =
|
|
336
|
+
this.orderByClause = `"${column}" ${direction}`;
|
|
160
337
|
return this;
|
|
161
338
|
}
|
|
162
339
|
|
|
@@ -171,31 +348,33 @@ export class SelectQuery<T = any> {
|
|
|
171
348
|
}
|
|
172
349
|
|
|
173
350
|
async execute(): Promise<T[]> {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
351
|
+
return await this.executeWithAutoClose(async () => {
|
|
352
|
+
if (!this.table) {
|
|
353
|
+
throw new Error('Table name is required. Use .from() method.');
|
|
354
|
+
}
|
|
177
355
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
356
|
+
let query = `SELECT ${this.columns} FROM "${this.table}"`;
|
|
357
|
+
|
|
358
|
+
if (this.whereClauses.length > 0) {
|
|
359
|
+
query += ` WHERE ${this.whereClauses.join(' AND ')}`;
|
|
360
|
+
}
|
|
183
361
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
362
|
+
if (this.orderByClause) {
|
|
363
|
+
query += ` ORDER BY ${this.orderByClause}`;
|
|
364
|
+
}
|
|
187
365
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
366
|
+
if (this.limitCount !== undefined) {
|
|
367
|
+
query += ` LIMIT ${this.limitCount}`;
|
|
368
|
+
}
|
|
191
369
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
370
|
+
if (this.offsetCount !== undefined) {
|
|
371
|
+
query += ` OFFSET ${this.offsetCount}`;
|
|
372
|
+
}
|
|
195
373
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
374
|
+
const executor = this.transaction || this.sql;
|
|
375
|
+
const result = await executeSql(executor, query, this.whereParams);
|
|
376
|
+
return Array.isArray(result) ? result : [];
|
|
377
|
+
});
|
|
199
378
|
}
|
|
200
379
|
|
|
201
380
|
async first(): Promise<T | null> {
|
|
@@ -203,28 +382,34 @@ export class SelectQuery<T = any> {
|
|
|
203
382
|
return results.length > 0 ? results[0]! : null;
|
|
204
383
|
}
|
|
205
384
|
|
|
385
|
+
async get(): Promise<T | null> {
|
|
386
|
+
return this.first();
|
|
387
|
+
}
|
|
388
|
+
|
|
206
389
|
async all(): Promise<T[]> {
|
|
207
390
|
return this.execute();
|
|
208
391
|
}
|
|
209
392
|
|
|
210
393
|
async count(): Promise<number> {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
394
|
+
return await this.executeWithAutoClose(async () => {
|
|
395
|
+
if (!this.table) {
|
|
396
|
+
throw new Error('Table name is required. Use .from() method.');
|
|
397
|
+
}
|
|
214
398
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
399
|
+
let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
|
|
400
|
+
|
|
401
|
+
if (this.whereClauses.length > 0) {
|
|
402
|
+
query += ` WHERE ${this.whereClauses.join(' AND ')}`;
|
|
403
|
+
}
|
|
220
404
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
405
|
+
const executor = this.transaction || this.sql;
|
|
406
|
+
const result = await executeSql(executor, query, this.whereParams);
|
|
407
|
+
// Handle different result formats
|
|
408
|
+
if (Array.isArray(result)) {
|
|
409
|
+
return result[0]?.count || 0;
|
|
410
|
+
}
|
|
411
|
+
return result.count || 0;
|
|
412
|
+
});
|
|
228
413
|
}
|
|
229
414
|
|
|
230
415
|
// Make the query awaitable by implementing then method
|
|
@@ -236,65 +421,76 @@ export class SelectQuery<T = any> {
|
|
|
236
421
|
}
|
|
237
422
|
}
|
|
238
423
|
|
|
239
|
-
export class UpdateQuery {
|
|
240
|
-
private sql: SQL;
|
|
241
|
-
private transaction?: any;
|
|
424
|
+
export class UpdateQuery extends BaseQuery {
|
|
242
425
|
private table: string;
|
|
243
426
|
private setClauses: string[] = [];
|
|
244
427
|
private setParams: any[] = [];
|
|
245
428
|
private whereClauses: string[] = [];
|
|
246
429
|
private whereParams: any[] = [];
|
|
247
430
|
|
|
248
|
-
constructor(sql: SQL, table: string, transaction?: any) {
|
|
249
|
-
|
|
250
|
-
this.transaction = transaction;
|
|
431
|
+
constructor(sql: SQL, table: string, transaction?: any, bunql?: BunQL) {
|
|
432
|
+
super(sql, transaction, bunql);
|
|
251
433
|
this.table = table;
|
|
252
434
|
}
|
|
253
435
|
|
|
254
436
|
set(data: Record<string, any>): this {
|
|
255
437
|
Object.entries(data).forEach(([key, value]) => {
|
|
256
|
-
this.setClauses.push(
|
|
438
|
+
this.setClauses.push(`"${key}" = ?`);
|
|
257
439
|
this.setParams.push(value);
|
|
258
440
|
});
|
|
259
441
|
return this;
|
|
260
442
|
}
|
|
261
443
|
|
|
262
444
|
where(column: string, operator: string, value: any): this {
|
|
263
|
-
|
|
264
|
-
|
|
445
|
+
if (operator.toUpperCase() === 'IN' && Array.isArray(value)) {
|
|
446
|
+
const placeholders = value.map(() => '?').join(', ');
|
|
447
|
+
this.whereClauses.push(`"${column}" IN (${placeholders})`);
|
|
448
|
+
this.whereParams.push(...value);
|
|
449
|
+
} else if (operator.toUpperCase() === 'NOT IN' && Array.isArray(value)) {
|
|
450
|
+
const placeholders = value.map(() => '?').join(', ');
|
|
451
|
+
this.whereClauses.push(`"${column}" NOT IN (${placeholders})`);
|
|
452
|
+
this.whereParams.push(...value);
|
|
453
|
+
} else {
|
|
454
|
+
this.whereClauses.push(`"${column}" ${operator} ?`);
|
|
455
|
+
this.whereParams.push(value);
|
|
456
|
+
}
|
|
265
457
|
return this;
|
|
266
458
|
}
|
|
267
459
|
|
|
268
460
|
async execute(): Promise<any> {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
461
|
+
return await this.executeWithAutoClose(async () => {
|
|
462
|
+
if (this.setClauses.length === 0) {
|
|
463
|
+
throw new Error('No values to update. Use .set() method.');
|
|
464
|
+
}
|
|
272
465
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
466
|
+
let query = `UPDATE "${this.table}" SET ${this.setClauses.join(', ')}`;
|
|
467
|
+
|
|
468
|
+
if (this.whereClauses.length > 0) {
|
|
469
|
+
query += ` WHERE ${this.whereClauses.join(' AND ')}`;
|
|
470
|
+
}
|
|
278
471
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
472
|
+
const executor = this.transaction || this.sql;
|
|
473
|
+
const allParams = [...this.setParams, ...this.whereParams];
|
|
474
|
+
return await executeSql(executor, query, allParams);
|
|
475
|
+
});
|
|
282
476
|
}
|
|
283
477
|
|
|
284
478
|
async count(): Promise<number> {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
479
|
+
return await this.executeWithAutoClose(async () => {
|
|
480
|
+
let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
|
|
481
|
+
|
|
482
|
+
if (this.whereClauses.length > 0) {
|
|
483
|
+
query += ` WHERE ${this.whereClauses.join(' AND ')}`;
|
|
484
|
+
}
|
|
290
485
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
486
|
+
const executor = this.transaction || this.sql;
|
|
487
|
+
const result = await executeSql(executor, query, this.whereParams);
|
|
488
|
+
// Handle different result formats
|
|
489
|
+
if (Array.isArray(result)) {
|
|
490
|
+
return result[0]?.count || 0;
|
|
491
|
+
}
|
|
492
|
+
return result.count || 0;
|
|
493
|
+
});
|
|
298
494
|
}
|
|
299
495
|
|
|
300
496
|
// Make the query awaitable by implementing then method
|
|
@@ -306,17 +502,16 @@ export class UpdateQuery {
|
|
|
306
502
|
}
|
|
307
503
|
}
|
|
308
504
|
|
|
309
|
-
export class InsertQuery {
|
|
310
|
-
private sql: SQL;
|
|
311
|
-
private transaction?: any;
|
|
505
|
+
export class InsertQuery extends BaseQuery {
|
|
312
506
|
private table: string;
|
|
313
507
|
private columns: string[] = [];
|
|
314
508
|
private valuesArray: any[] = [];
|
|
315
509
|
private isEmptyArray: boolean = false;
|
|
510
|
+
private isBulkInsert: boolean = false;
|
|
511
|
+
private bulkData: Record<string, any>[] = [];
|
|
316
512
|
|
|
317
|
-
constructor(sql: SQL, table: string, transaction?: any) {
|
|
318
|
-
|
|
319
|
-
this.transaction = transaction;
|
|
513
|
+
constructor(sql: SQL, table: string, transaction?: any, bunql?: BunQL) {
|
|
514
|
+
super(sql, transaction, bunql);
|
|
320
515
|
this.table = table;
|
|
321
516
|
}
|
|
322
517
|
|
|
@@ -330,11 +525,9 @@ export class InsertQuery {
|
|
|
330
525
|
const firstRow = data[0];
|
|
331
526
|
this.columns = Object.keys(firstRow);
|
|
332
527
|
|
|
333
|
-
data
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
});
|
|
337
|
-
});
|
|
528
|
+
// For bulk insert, store the data separately
|
|
529
|
+
this.isBulkInsert = true;
|
|
530
|
+
this.bulkData = data;
|
|
338
531
|
} else {
|
|
339
532
|
this.columns = Object.keys(data);
|
|
340
533
|
this.valuesArray = Object.values(data);
|
|
@@ -343,32 +536,65 @@ export class InsertQuery {
|
|
|
343
536
|
}
|
|
344
537
|
|
|
345
538
|
async execute(): Promise<any> {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
539
|
+
return await this.executeWithAutoClose(async () => {
|
|
540
|
+
if (this.isEmptyArray) {
|
|
541
|
+
throw new Error('Cannot insert empty array');
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (this.isBulkInsert) {
|
|
545
|
+
if (this.columns.length === 0 || this.bulkData.length === 0) {
|
|
546
|
+
throw new Error('No values to insert. Use .values() method.');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const columnsStr = this.columns.map(col => `"${col}"`).join(', ');
|
|
550
|
+
const placeholders = this.columns.map(() => '?').join(', ');
|
|
551
|
+
|
|
552
|
+
let query = `INSERT INTO "${this.table}" (${columnsStr}) VALUES (${placeholders})`;
|
|
553
|
+
|
|
554
|
+
const executor = this.transaction || this.sql;
|
|
555
|
+
|
|
556
|
+
// Execute each row separately for bulk insert
|
|
557
|
+
let totalAffectedRows = 0;
|
|
558
|
+
let lastInsertRowid: any = null;
|
|
559
|
+
|
|
560
|
+
for (const row of this.bulkData) {
|
|
561
|
+
const values = this.columns.map(col => row[col]);
|
|
562
|
+
const result = await executeSql(executor, query, values);
|
|
563
|
+
totalAffectedRows += result.affectedRows || 1;
|
|
564
|
+
lastInsertRowid = result.lastInsertRowid;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
affectedRows: totalAffectedRows,
|
|
569
|
+
lastInsertRowid: lastInsertRowid
|
|
570
|
+
};
|
|
571
|
+
} else {
|
|
572
|
+
if (this.columns.length === 0 || this.valuesArray.length === 0) {
|
|
573
|
+
throw new Error('No values to insert. Use .values() method.');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const columnsStr = this.columns.map(col => `"${col}"`).join(', ');
|
|
577
|
+
const placeholders = this.columns.map(() => '?').join(', ');
|
|
578
|
+
|
|
579
|
+
let query = `INSERT INTO "${this.table}" (${columnsStr}) VALUES (${placeholders})`;
|
|
580
|
+
|
|
581
|
+
const executor = this.transaction || this.sql;
|
|
582
|
+
return await executeSql(executor, query, this.valuesArray);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
361
585
|
}
|
|
362
586
|
|
|
363
587
|
async count(): Promise<number> {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
588
|
+
return await this.executeWithAutoClose(async () => {
|
|
589
|
+
let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
|
|
590
|
+
const executor = this.transaction || this.sql;
|
|
591
|
+
const result = await executeSql(executor, query, []);
|
|
592
|
+
// Handle different result formats
|
|
593
|
+
if (Array.isArray(result)) {
|
|
594
|
+
return result[0]?.count || 0;
|
|
595
|
+
}
|
|
596
|
+
return result.count || 0;
|
|
597
|
+
});
|
|
372
598
|
}
|
|
373
599
|
|
|
374
600
|
// Make the query awaitable by implementing then method
|
|
@@ -380,50 +606,61 @@ export class InsertQuery {
|
|
|
380
606
|
}
|
|
381
607
|
}
|
|
382
608
|
|
|
383
|
-
export class DeleteQuery {
|
|
384
|
-
private sql: SQL;
|
|
385
|
-
private transaction?: any;
|
|
609
|
+
export class DeleteQuery extends BaseQuery {
|
|
386
610
|
private table: string;
|
|
387
611
|
private whereClauses: string[] = [];
|
|
388
612
|
private whereParams: any[] = [];
|
|
389
613
|
|
|
390
|
-
constructor(sql: SQL, table: string, transaction?: any) {
|
|
391
|
-
|
|
392
|
-
this.transaction = transaction;
|
|
614
|
+
constructor(sql: SQL, table: string, transaction?: any, bunql?: BunQL) {
|
|
615
|
+
super(sql, transaction, bunql);
|
|
393
616
|
this.table = table;
|
|
394
617
|
}
|
|
395
618
|
|
|
396
619
|
where(column: string, operator: string, value: any): this {
|
|
397
|
-
|
|
398
|
-
|
|
620
|
+
if (operator.toUpperCase() === 'IN' && Array.isArray(value)) {
|
|
621
|
+
const placeholders = value.map(() => '?').join(', ');
|
|
622
|
+
this.whereClauses.push(`"${column}" IN (${placeholders})`);
|
|
623
|
+
this.whereParams.push(...value);
|
|
624
|
+
} else if (operator.toUpperCase() === 'NOT IN' && Array.isArray(value)) {
|
|
625
|
+
const placeholders = value.map(() => '?').join(', ');
|
|
626
|
+
this.whereClauses.push(`"${column}" NOT IN (${placeholders})`);
|
|
627
|
+
this.whereParams.push(...value);
|
|
628
|
+
} else {
|
|
629
|
+
this.whereClauses.push(`"${column}" ${operator} ?`);
|
|
630
|
+
this.whereParams.push(value);
|
|
631
|
+
}
|
|
399
632
|
return this;
|
|
400
633
|
}
|
|
401
634
|
|
|
402
635
|
async execute(): Promise<any> {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
636
|
+
return await this.executeWithAutoClose(async () => {
|
|
637
|
+
let query = `DELETE FROM "${this.table}"`;
|
|
638
|
+
|
|
639
|
+
if (this.whereClauses.length > 0) {
|
|
640
|
+
query += ` WHERE ${this.whereClauses.join(' AND ')}`;
|
|
641
|
+
}
|
|
408
642
|
|
|
409
|
-
|
|
410
|
-
|
|
643
|
+
const executor = this.transaction || this.sql;
|
|
644
|
+
return await executeSql(executor, query, this.whereParams);
|
|
645
|
+
});
|
|
411
646
|
}
|
|
412
647
|
|
|
413
648
|
async count(): Promise<number> {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
649
|
+
return await this.executeWithAutoClose(async () => {
|
|
650
|
+
let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
|
|
651
|
+
|
|
652
|
+
if (this.whereClauses.length > 0) {
|
|
653
|
+
query += ` WHERE ${this.whereClauses.join(' AND ')}`;
|
|
654
|
+
}
|
|
419
655
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
656
|
+
const executor = this.transaction || this.sql;
|
|
657
|
+
const result = await executeSql(executor, query, this.whereParams);
|
|
658
|
+
// Handle different result formats
|
|
659
|
+
if (Array.isArray(result)) {
|
|
660
|
+
return result[0]?.count || 0;
|
|
661
|
+
}
|
|
662
|
+
return result.count || 0;
|
|
663
|
+
});
|
|
427
664
|
}
|
|
428
665
|
|
|
429
666
|
// Make the query awaitable by implementing then method
|