querize 1.0.9 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +755 -583
  2. package/cjs/drivers/drv_mariadb.js +18 -2
  3. package/cjs/drivers/drv_mariadb.js.map +1 -1
  4. package/cjs/drivers/drv_mysql2.js +19 -2
  5. package/cjs/drivers/drv_mysql2.js.map +1 -1
  6. package/cjs/drivers/drv_oracledb.js +20 -9
  7. package/cjs/drivers/drv_oracledb.js.map +1 -1
  8. package/cjs/drivers/index.js +1 -14
  9. package/cjs/drivers/index.js.map +1 -1
  10. package/cjs/types/drivers/drv_mariadb.d.ts +1 -1
  11. package/cjs/types/drivers/drv_mariadb.d.ts.map +1 -1
  12. package/cjs/types/drivers/drv_mysql2.d.ts +1 -1
  13. package/cjs/types/drivers/drv_mysql2.d.ts.map +1 -1
  14. package/cjs/types/drivers/drv_oracledb.d.ts +5 -5
  15. package/cjs/types/drivers/drv_oracledb.d.ts.map +1 -1
  16. package/cjs/types/drivers/index.d.ts +6 -0
  17. package/cjs/types/drivers/index.d.ts.map +1 -1
  18. package/esm/drivers/drv_mariadb.js +18 -2
  19. package/esm/drivers/drv_mariadb.js.map +1 -1
  20. package/esm/drivers/drv_mysql2.js +19 -2
  21. package/esm/drivers/drv_mysql2.js.map +1 -1
  22. package/esm/drivers/drv_oracledb.js +20 -9
  23. package/esm/drivers/drv_oracledb.js.map +1 -1
  24. package/esm/drivers/index.js +1 -14
  25. package/esm/drivers/index.js.map +1 -1
  26. package/esm/types/drivers/drv_mariadb.d.ts +1 -1
  27. package/esm/types/drivers/drv_mariadb.d.ts.map +1 -1
  28. package/esm/types/drivers/drv_mysql2.d.ts +1 -1
  29. package/esm/types/drivers/drv_mysql2.d.ts.map +1 -1
  30. package/esm/types/drivers/drv_oracledb.d.ts +5 -5
  31. package/esm/types/drivers/drv_oracledb.d.ts.map +1 -1
  32. package/esm/types/drivers/index.d.ts +6 -0
  33. package/esm/types/drivers/index.d.ts.map +1 -1
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -1,583 +1,755 @@
1
- # Querize.js
2
-
3
- A modern TypeScript/JavaScript query builder and database abstraction layer that provides a fluent interface for building and executing SQL queries across different database drivers.
4
-
5
- ## Features
6
-
7
- - 🔥 **TypeScript Support** - Built with TypeScript for better type safety
8
- - 🚀 **Multiple Connection Types** - Support for single connections, connection pools, and clusters
9
- - 🔄 **Transaction Management** - Built-in transaction support with commit/rollback
10
- - 📝 **Query Builder** - Fluent API for building complex SQL queries
11
- - 🔌 **Driver Abstraction** - Pluggable driver system for different databases
12
- - 🎯 **Promise-based** - Modern async/await support
13
- - 🔒 **Database Locking** - Built-in support for named locks
14
-
15
- ## Installation
16
-
17
- ### npm
18
- ```bash
19
- npm install querize
20
- ```
21
-
22
- ### yarn
23
- ```bash
24
- yarn add querize
25
- ```
26
-
27
- ### pnpm
28
- ```bash
29
- pnpm add querize
30
- ```
31
-
32
- ## Quick Start (typescript)
33
-
34
- ```typescript
35
- import Querize from 'querize';
36
-
37
- // Create a database instance
38
- const querize = new Querize('mysql'); // or your preferred driver
39
-
40
- // Create a connection pool
41
- const database = await querize.createPool({
42
- host: 'localhost',
43
- user: 'root',
44
- password: 'password',
45
- database: 'mydb'
46
- });
47
-
48
- // Simple query
49
- const users = await database
50
- .singleton()
51
- .then(query => query
52
- .table('users')
53
- .where({'active' : 1})
54
- .select('id', 'name', 'email')
55
- .execute()
56
- );
57
-
58
- ```
59
- ## Quick Start (javascript)
60
-
61
- ```typescript
62
- const Querize = require('querize');
63
-
64
- // Create a database instance
65
- const querize = new Querize('mysql'); // or your preferred driver
66
-
67
- // Create a connection pool and query
68
- querize.createPool({
69
- host: 'localhost',
70
- user: 'root',
71
- password: 'password',
72
- database: 'mydb'
73
- })
74
- .then(function(database) {
75
- return database.singleton()
76
- })
77
- .then(function(query) {
78
- return query.table('users')
79
- .where({'active' : 1})
80
- .select('id', 'name', 'email')
81
- .execute()
82
- });
83
- ```
84
-
85
- ## Connection Types
86
-
87
- ### Single Connection
88
- - **Behavior**: Creates a new connection for each query and closes it immediately after execution.
89
- - **Flow**:
90
- SQL execute connect query disconnect → SQL → ...
91
- - **Notes**: Very simple, but repeatedly opening and closing connections can cause performance overhead.
92
- ```typescript
93
- const database = await querize.createConnect(options);
94
- ```
95
-
96
- ### Connection Pool
97
- - **Behavior**: Uses a connection pool where multiple queries can reuse existing connections.
98
- - **Flow**:
99
- SQL → execute → connect → query → SQL → ...
100
- - **Notes**: More efficient for high-frequency queries since connections are reused instead of created and destroyed each time.
101
- ```typescript
102
- const database = await querize.createPool(options);
103
- ```
104
-
105
- ### Cluster
106
- - **Behavior**: Manages multiple database instances and selectively connects to one depending on the query.
107
- - **Flow**:
108
- SQL → execute → connect [choose DB] → query → SQL → ...
109
- - **Notes**: Useful in multi-database environments for load balancing or failover scenarios.
110
- ```typescript
111
- const database = await querize.createCluster([option1, option2, ...]);
112
- ```
113
-
114
- ### Query-only (for Debug)
115
- - **Notes**: Method to inspect the SQL string of a query.
116
- ```typescript
117
- const database = await querize.createQuery();
118
- ```
119
-
120
- ## Transaction Types
121
-
122
- ### `Singleton`
123
- - **Behavior**: Each connection handles only a single SQL statement at a time.
124
- - **Use case**: Simplifies query execution when only one statement per connection is required.
125
-
126
- ### `Transaction`
127
- - **Behavior**: A single connection is used for multiple statements until an explicit `COMMIT` or `ROLLBACK` is executed.
128
- - **Use case**: Ensures atomic operations and consistency across multiple queries.
129
-
130
- ## Usage Examples
131
-
132
- ### Basic Queries
133
-
134
- #### SELECT
135
- ```typescript
136
- // Simple select
137
- const users = await database.singleton()
138
- .then(q => q.table('users')
139
- .select()
140
- .execute());
141
-
142
- // Select with conditions
143
- const activeUsers = await database.singleton()
144
- .then(q => q.table('users')
145
- .where({
146
- 'active' : 1,
147
- 'created_at', "> '2023-01-01'"
148
- })
149
- .select('id', 'name', 'email')
150
- .execute());
151
-
152
- // Select with joins
153
- const userPosts = await database.singleton()
154
- .then(q => q.table('users', 'u')
155
- .inner('posts', 'p', {'u.id = p.user_id'})
156
- .select('u.name', 'p.title')
157
- .execute());
158
- ```
159
-
160
- #### INSERT
161
- ```typescript
162
- const result = await database.singleton()
163
- .then(q => q.table('users')
164
- .insert({
165
- name: 'John Doe',
166
- email: 'john@example.com',
167
- active: 1
168
- })
169
- .execute());
170
-
171
- // Insert with ON DUPLICATE KEY UPDATE
172
- const result = await database.singleton()
173
- .then(q => q.table('users')
174
- .insert({
175
- name: 'John Doe',
176
- email: 'john@example.com'
177
- }, { ignore: false })
178
- .on('DUPLICATE', { updated_at: new Date() })
179
- .execute());
180
- ```
181
-
182
- #### UPDATE
183
- ```typescript
184
- const result = await database.singleton()
185
- .then(q => q.table('users')
186
- .where({'id' : 1})
187
- .update({
188
- name: 'Jane Doe',
189
- updated_at: new Date()
190
- }));
191
- ```
192
-
193
- #### DELETE
194
- ```typescript
195
- const result = await database.singleton()
196
- .then(q => q.table('users')
197
- .where({'active' : 0})
198
- .delete());
199
- ```
200
-
201
- ### Advanced Features
202
-
203
- #### Transactions
204
- ```typescript
205
- const transaction = await database.transaction();
206
-
207
- try {
208
- await transaction.table('users')
209
- .insert({ name: 'John', email: 'john@example.com' })
210
- .execute();
211
-
212
- await transaction.table('user_profiles')
213
- .insert({ user_id: 1, bio: 'Hello world' })
214
- .execute();
215
-
216
- await transaction.commit();
217
- } catch (error) {
218
- await transaction.rollback();
219
- throw error;
220
- }
221
- ```
222
-
223
- #### Subqueries
224
- ```typescript
225
- const query = await database.singleton()
226
- const result = await query.table('users')
227
- .left('order', query.table('orders')
228
- .where({'status' : 'completed'})
229
- .select('user_id'), {
230
- 'order.user_id' : '= users.id'
231
- }
232
- )
233
- .where({'id' : 'john'})
234
- .select()
235
- .execute();
236
- ```
237
-
238
- #### Joins
239
- ```typescript
240
- const result = await query.table('users', 'u')
241
- .left('profiles', 'p', {'u.id = p.user_id'})
242
- .right('settings', 's', {'u.id = s.user_id'})
243
- .select('u.name', 'p.bio', 's.theme')
244
- .execute();
245
- ```
246
-
247
- #### Grouping and Ordering
248
- ```typescript
249
- const stats = await query.table('orders')
250
- .group_by('status')
251
- .order_by('count DESC')
252
- .select('status', 'COUNT(*) as count')
253
- .execute();
254
- ```
255
-
256
- #### Pagination
257
- ```typescript
258
- // Limit only
259
- const recent = await query.table('posts')
260
- .order_by('created_at DESC')
261
- .limit(10)
262
- .select()
263
- .execute();
264
-
265
- // Offset and limit
266
- const page2 = await query.table('posts')
267
- .order_by('created_at DESC')
268
- .limit(10, 10) // offset 10, limit 10
269
- .select()
270
- .execute();
271
- ```
272
-
273
- #### Database Locking
274
- ```typescript
275
- const query = await database.transaction();
276
-
277
- try {
278
- // Acquire lock
279
- await query.lock('user_update', 30); // 30 second timeout
280
-
281
- // Do critical operations
282
- await query.table('users')
283
- .where('id', 1)
284
- .update({ balance: 1000 })
285
- .execute();
286
-
287
- // Release lock
288
- await query.unlock('user_update');
289
-
290
- await query.commit();
291
- } catch (error) {
292
- await query.rollback();
293
- }
294
- ```
295
-
296
- ## Configuration
297
-
298
- ### Database Options
299
- ```typescript
300
- interface MQOption {
301
- host: string;
302
- port?: number;
303
- user: string;
304
- password: string;
305
- database: string;
306
- charset?: string;
307
- timeout?: number;
308
- // ... other driver-specific options
309
- }
310
- ```
311
-
312
- ### Debugging
313
- Enable query logging:
314
- ```typescript
315
- import { setTrace } from 'querize';
316
-
317
- setTrace((message) => {
318
- console.log('[Querize]', message);
319
- });
320
- ```
321
-
322
- ## Error Handling
323
-
324
- ```typescript
325
- try {
326
- const result = await database.singleton()
327
- .then(q => q.table('users')
328
- .select()
329
- .execute());
330
- } catch (error) {
331
- console.error('Query failed:', error);
332
- }
333
- ```
334
-
335
- # Query Builder – `where()`
336
-
337
- The `where()` method is used to build **SQL WHERE conditions** in a declarative way.
338
- It accepts **objects** or **arrays of objects** as input and supports `AND` / `OR` composition.
339
- You can also use `query.literal()` to safely insert raw SQL fragments when necessary.
340
-
341
- ---
342
-
343
- ## Signature
344
-
345
- ```ts
346
- where(...clauses: (object | object[])[]): MQWhere
347
-
348
- // Start a grouped condition
349
- query.where(...clauses: (object | object[])[]): MQWhere
350
-
351
- // Add OR conditions to a group
352
- MQWhere.or(...clauses: (object | object[])[]): MQWhere
353
- ```
354
-
355
- ---
356
-
357
- ## Value Rules
358
-
359
- * **Single value** → equals (`=`)
360
-
361
- ```js
362
- .where({ user_id: 1 }) // user_id = 1
363
- ```
364
- * **Operator string** used as-is
365
- (`"> 0"`, `"<>'H'"`, `"= other.col"`)
366
-
367
- ```js
368
- .where({ amount: '> 1000' }) // amount > 1000
369
- ```
370
- * **Array** `IN (...)`
371
-
372
- ```js
373
- .where({ status: ['ACTIVE', 'INACTIVE'] }) // status IN ('ACTIVE','INACTIVE')
374
- ```
375
- * **Array + literal** → multiple alternatives (e.g., including `NULL`)
376
-
377
- ```js
378
- .where({ account_id: [123, query.literal('IS NULL')] })
379
- // account_id = 123 OR account_id IS NULL
380
- ```
381
- * **`query.literal(sql)`** → raw SQL fragment (for LIKE, IS NULL, etc.)
382
-
383
- ---
384
-
385
- ## Combination Rules
386
-
387
- * Multiple arguments → **AND**
388
-
389
- ```js
390
- .where({ a: 1 }, { b: 2 }) // a=1 AND b=2
391
- ```
392
-
393
- * Fields in an object **AND**
394
-
395
- ```js
396
- .where({ a: 1, b: 2 }) // a=1 AND b=2
397
- ```
398
-
399
- * Array of objects → **OR**
400
-
401
- ```js
402
- .where([{ a: 1 }, { b: 2 }]) // (a=1 OR b=2)
403
- ```
404
-
405
- ---
406
-
407
- ## Examples
408
-
409
- ### Equality
410
-
411
- ```js
412
- .where({ user_id: 100 }) // user_id = 100
413
- ```
414
-
415
- ### Date range
416
-
417
- ```js
418
- .where(
419
- { created_at: '>= "2025-01-01"' },
420
- { created_at: '<= "2025-01-31"' },
421
- ) // created_at >= "2025-01-01" AND created_at <= "2025-01-31"
422
- ```
423
-
424
- ### Allow NULL
425
-
426
- ```js
427
- .where({ account_id: [body.account_id, query.literal('IS NULL')] })
428
- // account_id = '...' OR account_id IS NULL
429
- ```
430
-
431
- ### OR group
432
-
433
- ```js
434
- .where([
435
- { account_type: 'SAVINGS' },
436
- { account_type: 'ASSET' },
437
- ])
438
- // account_type = 'SAVINGS' OR account_type = 'ASSET'
439
- ```
440
-
441
- ### LIKE search
442
-
443
- ```js
444
- .where([
445
- { title: query.literal("LIKE '%keyword%'") },
446
- { memo : query.literal("LIKE '%keyword%'") },
447
- ])
448
- // title LIKE '%keyword%' OR memo LIKE '%keyword%'
449
- ```
450
-
451
- ### Group + OR extension
452
-
453
- ```js
454
- let dateRange = query.where(
455
- { t.date: '>= "2025-01-01"' },
456
- { t.date: '<= "2025-01-31"' },
457
- );
458
-
459
- // Include overlapping installment period
460
- dateRange = dateRange.or({
461
- t.date : '<= "2025-01-31"',
462
- t.end_date: '>= "2025-01-01"',
463
- });
464
- // (t.date >= "2025-01-01" AND t.date <= "2025-01-31")
465
- // OR
466
- // (t.date <= "2025-01-31" AND t.end_date >= "2025-01-01")
467
- ```
468
-
469
- ---
470
-
471
- ## Full Example – **Monthly Transaction List**
472
-
473
- ```js
474
- query.table('transactions', 't')
475
- .left('accounts', 'a', {
476
- 'a.user_id' : req.session.user_id,
477
- 'a.account_id' : '= t.account_id',
478
- 'a.account_type': ['SAVINGS','ASSET'],
479
- })
480
- .where(
481
- // User condition
482
- { 't.user_id': req.session.user_id },
483
-
484
- // Date range
485
- { 't.date': '>= "2025-01-01"' },
486
- { 't.date': '<= "2025-01-31"' },
487
-
488
- // Account check (include NULL)
489
- {
490
- 'a.account_id' : ['= t.account_id', query.literal('IS NULL')],
491
- 'a.account_type': ['SAVINGS','ASSET', query.literal('IS NULL')],
492
- },
493
- )
494
- .order_by('t.date', 't.time', 't.description')
495
- .select([
496
- 'a.account_name AS Account',
497
- 't.currency AS Currency',
498
- 't.amount AS Amount',
499
- 't.description AS Description',
500
- 't.date AS Date',
501
- 't.time AS Time',
502
- 't.category_code AS CategoryCode',
503
- 't.memo AS Memo',
504
- ])
505
- .execute();
506
- ```
507
-
508
- ```sql
509
- SELECT
510
- a.account_name AS Account,
511
- t.currency AS Currency,
512
- t.amount AS Amount,
513
- t.description AS Description,
514
- t.date AS Date,
515
- t.time AS Time,
516
- t.category_code AS CategoryCode,
517
- t.memo AS Memo
518
- FROM
519
- transactions AS t
520
- LEFT OUTER JOIN accounts AS a ON (
521
- a.user_id = 'tester' AND a.account_id = t.account_id
522
- AND (a.account_type = 'SAVINGS' OR a.account_type = 'ASSET')
523
- )
524
- WHERE
525
- (
526
- (t.user_id = 'tester')
527
- AND (t.date >= "2025-01-01")
528
- AND (t.date <= "2025-01-31")
529
- AND (
530
- (a.account_id = t.account_id OR a.account_id IS NULL)
531
- AND (
532
- a.account_type = 'SAVINGS' OR a.account_type = 'ASSET' OR a.account_type IS NULL
533
- )
534
- )
535
- )
536
- ORDER BY
537
- t.date,
538
- t.time,
539
- t.description
540
- ```
541
-
542
- ---
543
-
544
- ## Quick Reference
545
-
546
- | Pattern | Example | SQL Result |
547
- | --------------- | ----------------------------------- | ------------------------ |
548
- | Single value | `{ col: 1 }` | `col = 1` |
549
- | Operator string | `{ amt: '> 0' }` | `amt > 0` |
550
- | IN | `{ type: ['S','A'] }` | `type IN ('S','A')` |
551
- | NULL include | `{ k: [val, literal('IS NULL')] }` | `k=val OR k IS NULL` |
552
- | LIKE | `{ memo: literal("LIKE '%foo%'") }` | `memo LIKE '%foo%'` |
553
- | OR group | `where([{A}, {B}])` | `(A OR B)` |
554
- | Group OR | `where({A}).or({B}, [{C},{D}])` | `(A) OR (B) OR (C OR D)` |
555
-
556
- ---
557
-
558
- ## Best Practices
559
-
560
- * Use **objects and arrays** as much as possible for clarity.
561
- * Reserve `query.literal()` only for cases that **cannot be expressed as values** (e.g., `LIKE`, `IS NULL`, functions).
562
- * Always prefer **date range queries** (`>= start AND <= end`) for better index usage.
563
- * Use **object arrays** for OR conditions to keep code clean.
564
-
565
-
566
-
567
-
568
-
569
- ## Best Practices
570
-
571
- 1. **Always use transactions for multiple related operations**
572
- 2. **Clean up connections when done**
573
- 3. **Use connection pools for better performance**
574
- 4. **Handle errors appropriately**
575
- 5. **Use parameterized queries to prevent SQL injection**
576
-
577
- ## License
578
-
579
- MIT License - see LICENSE file for details.
580
-
581
- ## Support
582
-
583
- For issues and questions, please use the [GitHub Issues](https://github.com/itfin-git/Querize/issues) page.
1
+ # Querize.js
2
+
3
+ > **MySQL / MariaDB / Oracle** query builder for Node.js
4
+ > Promise-based · Fluent API · Connection Pool / Cluster · Transaction support
5
+
6
+ [![npm](https://img.shields.io/npm/v/querize)](https://www.npmjs.com/package/querize)
7
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
8
+
9
+ ---
10
+
11
+ ## Table of Contents
12
+
13
+ - [Overview](#overview)
14
+ - [Installation](#installation)
15
+ - [Quick Start](#quick-start)
16
+ - [initialize()](#initialize)
17
+ - [Connection Modes](#connection-modes)
18
+ - [createConnect](#createconnect)
19
+ - [createPool](#createpool)
20
+ - [createCluster](#createcluster)
21
+ - [Operation Modes](#operation-modes)
22
+ - [transaction()](#transaction)
23
+ - [singleton()](#singleton)
24
+ - [Query API](#query-api)
25
+ - [Table / JOIN](#table--join)
26
+ - [WHERE](#where)
27
+ - [SELECT](#select)
28
+ - [INSERT](#insert)
29
+ - [UPDATE](#update)
30
+ - [DELETE](#delete)
31
+ - [Modifiers](#modifiers)
32
+ - [Sub-queries](#sub-queries)
33
+ - [ResultSet](#resultset)
34
+ - [Oracle Driver](#oracle-driver)
35
+ - [Trace / Logging](#trace--logging)
36
+
37
+ ---
38
+
39
+ ## Overview
40
+
41
+ Querize.js is a lightweight, promise-based SQL query builder for Node.js targeting MySQL, MariaDB, and Oracle databases.
42
+ It lets you compose queries through a fluent chaining API without writing raw SQL strings.
43
+
44
+ ```
45
+ Querize → MQDatabase → MQQuery → MQWhere → execute()
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ # Install Querize
54
+ npm install querize
55
+
56
+ # Install your database driver
57
+ npm install mysql2 # MySQL
58
+ npm install mariadb # MariaDB
59
+ npm install oracledb # Oracle
60
+ ```
61
+
62
+ > Drivers are loaded dynamically — install only the one you need.
63
+
64
+ ---
65
+
66
+ ## Quick Start
67
+
68
+ ```javascript
69
+ import { Querize } from 'querize';
70
+
71
+ // 1. Create a Querize instance with your driver
72
+ const qz = new Querize('mysql2'); // 'mysql2' | 'mariadb' | 'oracle'
73
+
74
+ // 2. Initialize the driver (always recommended)
75
+ await qz.initialize();
76
+
77
+ // 3. Create a connection pool
78
+ const Database = await qz.createPool({
79
+ alias: 'main',
80
+ host: 'localhost',
81
+ user: 'root',
82
+ password: 'password',
83
+ database: 'mydb',
84
+ connectionLimit: 10,
85
+ });
86
+
87
+ // 4. Run a query
88
+ const query = await Database.singleton();
89
+ const result = await query.table('users').where({ id: 1 }).select().execute();
90
+ console.log(result.rows); // [{ id: 1, name: 'Alice', ... }]
91
+
92
+ // 5. Shutdown
93
+ await Database.destroy();
94
+ ```
95
+
96
+ ---
97
+
98
+ ## initialize()
99
+
100
+ `qz.initialize(option?)` initializes the driver. Call it **once at application startup**, before creating any connection.
101
+
102
+ **It is recommended to always call `initialize()` regardless of which driver you use.**
103
+ For mysql2 and mariadb, it resolves immediately with no side effects.
104
+ For Oracle, it sets up the Instant Client path at this stage.
105
+ This keeps your initialization flow consistent and makes driver swaps seamless.
106
+
107
+ ### Signature
108
+
109
+ ```typescript
110
+ qz.initialize(option?: { libDir?: string }): Promise<void>
111
+ ```
112
+
113
+ ### Parameters
114
+
115
+ | Parameter | Driver | Description |
116
+ |-----------|--------|-------------|
117
+ | `option.libDir` | Oracle only | Path to the Oracle Instant Client library. Required for Thick mode. |
118
+ | _(none)_ | mysql2 / mariadb | Call with no arguments. Resolves immediately with no internal processing. |
119
+
120
+ ### Behavior by Driver
121
+
122
+ | Driver | Behavior |
123
+ |--------|----------|
124
+ | `mysql2` | Resolves immediately. No initialization required. |
125
+ | `mariadb` | Resolves immediately. No initialization required. |
126
+ | `oracle` | Calls `oracledb.initOracleClient()` if `libDir` is provided (Thick mode). Otherwise runs in Thin mode. |
127
+
128
+ ### Examples
129
+
130
+ ```javascript
131
+ import { Querize } from 'querize';
132
+
133
+ // mysql2 / mariadb — call with no arguments
134
+ const qz = new Querize('mysql2');
135
+ await qz.initialize();
136
+ const Database = await qz.createPool({ ... });
137
+
138
+ // Oracle — Thin mode (no Instant Client required, oracledb v6+)
139
+ const qz = new Querize('oracle');
140
+ await qz.initialize();
141
+ const Database = await qz.createPool({ ... });
142
+
143
+ // Oracle Thick mode (provide Instant Client path)
144
+ const qz = new Querize('oracle');
145
+ await qz.initialize({ libDir: '/oracle' });
146
+ const Database = await qz.createPool({ ... });
147
+ ```
148
+
149
+ > 💡 **Always call `initialize()` regardless of driver — it is the recommended pattern.**
150
+ > Even when using mysql2 or mariadb, the call is a no-op and exits immediately.
151
+ > Fix it in your app startup routine as shown below for portability:
152
+ >
153
+ > ```javascript
154
+ > const qz = new Querize(process.env.DB_DRIVER);
155
+ > await qz.initialize({ libDir: process.env.ORACLE_LIB_DIR });
156
+ > // → mysql2/mariadb: libDir is ignored, resolves immediately
157
+ > // → oracle: initializes in Thick mode
158
+ > ```
159
+
160
+ ---
161
+
162
+ ## Connection Modes
163
+
164
+ Choose how the `Querize` instance manages database connections. All three return `Promise<MQDatabase>`.
165
+
166
+ ### createConnect
167
+
168
+ **Single direct connection.** Opens a new TCP connection for every operation and closes it when done.
169
+ Best suited for scripts, CLIs, and migration tools where connection overhead is not a concern.
170
+
171
+ ```javascript
172
+ const Database = await qz.createConnect({
173
+ alias: 'main',
174
+ host: 'localhost',
175
+ user: 'dbuser',
176
+ password: 'secret',
177
+ database: 'mydb',
178
+ });
179
+ ```
180
+
181
+ > ⚠️ Avoid in web servers — each request opens and closes its own TCP connection, which is expensive under concurrent load.
182
+
183
+ ---
184
+
185
+ ### createPool
186
+
187
+ **Connection pool.** Maintains a set of reusable connections. **Recommended for web servers and APIs.**
188
+
189
+ ```javascript
190
+ const Database = await qz.createPool({
191
+ alias: 'main',
192
+ host: 'localhost',
193
+ user: 'dbuser',
194
+ password: 'secret',
195
+ database: 'mydb',
196
+ connectionLimit: 10, // max simultaneous connections
197
+ });
198
+ ```
199
+
200
+ | Option | Description |
201
+ |--------|-------------|
202
+ | `alias` | Logical name used for cluster routing |
203
+ | `host` | Database server hostname or IP |
204
+ | `user` / `password` | Authentication credentials |
205
+ | `database` | Default schema |
206
+ | `connectionLimit` | Maximum pool size (recommended: 10–50) |
207
+ | `dateStrings` | Return DATE/DATETIME columns as strings instead of JS Date objects |
208
+ | `supportBigNumbers` | Handle BIGINT columns without precision loss |
209
+
210
+ ---
211
+
212
+ ### createCluster
213
+
214
+ **Pool cluster.** Groups multiple pools (e.g. primary + replicas) behind a single database object.
215
+ Used for read/write splitting and high-availability setups. Each entry must have a unique `alias`.
216
+
217
+ ```javascript
218
+ const Database = await qz.createCluster([
219
+ {
220
+ alias: 'MASTER',
221
+ host: '10.0.0.1',
222
+ user: 'dbuser',
223
+ password: 'secret',
224
+ database: 'mydb',
225
+ connectionLimit: 5,
226
+ },
227
+ {
228
+ alias: 'SLAVE01',
229
+ host: '10.0.0.2',
230
+ user: 'dbuser',
231
+ password: 'secret',
232
+ database: 'mydb',
233
+ connectionLimit: 10,
234
+ },
235
+ ]);
236
+ ```
237
+
238
+ Pass `dbmode` to `transaction()` / `singleton()` to route to a specific node.
239
+
240
+ ```javascript
241
+ const trx = await Database.transaction('mydb', 'MASTER'); // writes MASTER
242
+ const q = await Database.singleton('mydb', 'SLAVE01'); // reads → SLAVE01
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Operation Modes
248
+
249
+ Controls how connections are acquired and released during query execution.
250
+
251
+ ### transaction()
252
+
253
+ Acquires a dedicated connection, executes `BEGIN`, and returns an `MQQuery` instance.
254
+ All subsequent queries through this object run on the **same connection inside the same transaction**.
255
+ You must call `commit()` or `rollback()` explicitly to end the transaction and release the connection.
256
+
257
+ ```javascript
258
+ console.log(`# querize : transaction`);
259
+ {
260
+ const query = await Database.transaction('example', 'master');
261
+
262
+ let result;
263
+
264
+ result = await query.table('tbl_student').where({ stdid: 10 }).select().execute();
265
+ console.log('student select1 schid:', result.rows);
266
+
267
+ result = await query.table('tbl_student').where({ stdid: 10 }).update({ schid: 10 }).execute();
268
+ console.log('student update1:', result.affected);
269
+
270
+ result = await query.table('tbl_student').where({ stdid: 10 }).select().execute();
271
+ console.log('student select2 schid:', result.rows[0].schid);
272
+
273
+ result = await query.table('tbl_student').where({ stdid: 10 }).update({ schid: 1 }).execute();
274
+ console.log('student update2:', result.affected);
275
+
276
+ result = await query.table('tbl_student').where({ stdid: 10 }).select().execute();
277
+ console.log('student select3 schid:', result.rows[0].schid);
278
+
279
+ await query.commit();
280
+ }
281
+ ```
282
+
283
+ Always wrap in `try/catch` and call `rollback()` on error.
284
+
285
+ ```javascript
286
+ const query = await Database.transaction('mydb', 'master');
287
+ try {
288
+ await query.table('accounts').where({ id: 1 }).update({ balance: '= balance - 100' }).execute();
289
+ await query.table('accounts').where({ id: 2 }).update({ balance: '= balance + 100' }).execute();
290
+ await query.commit();
291
+ } catch (err) {
292
+ await query.rollback();
293
+ throw err;
294
+ }
295
+ ```
296
+
297
+ **Signature**
298
+
299
+ ```typescript
300
+ Database.transaction(dbname?: string, dbmode?: string): Promise<MQQuery>
301
+ ```
302
+
303
+ | Parameter | Description |
304
+ |-----------|-------------|
305
+ | `dbname` | If provided, executes `USE <dbname>` immediately after connecting |
306
+ | `dbmode` | Selects a specific cluster node by alias |
307
+
308
+ ---
309
+
310
+ ### singleton()
311
+
312
+ Returns an `MQQuery` instance.
313
+ Each call to `execute()` **independently** acquires a connection, runs the SQL, and releases it back to the pool.
314
+ There is no persistent connection between calls. Use this for independent, non-transactional queries.
315
+
316
+ ```javascript
317
+ const query = await Database.singleton();
318
+
319
+ // Each execute() uses its own connection
320
+ const result1 = await query.table('users').where({ active: 1 }).select().execute();
321
+ const result2 = await query.table('orders').where({ user_id: 1 }).select().execute();
322
+
323
+ console.log(result1.rows);
324
+ console.log(result2.rows);
325
+ ```
326
+
327
+ **Signature**
328
+
329
+ ```typescript
330
+ Database.singleton(dbname?: string, dbmode?: string): Promise<MQQuery>
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Query API
336
+
337
+ ### Table / JOIN
338
+
339
+ ```javascript
340
+ query.table('users') // FROM users
341
+ query.table('users', 'u') // FROM users u (alias)
342
+
343
+ query.table('users', 'u')
344
+ .inner('orders', 'o', { 'u.id': 'o.user_id' }) // INNER JOIN
345
+
346
+ query.table('users', 'u')
347
+ .left('orders', 'o', { 'u.id': 'o.user_id' }) // LEFT OUTER JOIN
348
+
349
+ query.table('users', 'u')
350
+ .right('orders', 'o', { 'u.id': 'o.user_id' }) // RIGHT OUTER JOIN
351
+ ```
352
+
353
+ ---
354
+
355
+ ### WHERE
356
+
357
+ Pass a plain object to `where()`. Keys are column names, values are match conditions.
358
+
359
+ ```javascript
360
+ // Equality
361
+ query.table('users').where({ id: 1, active: 1 })
362
+ // → WHERE (id = 1 AND active = 1)
363
+
364
+ // Comparison operators prefix the value with the operator
365
+ query.table('orders').where({ amount: '> 1000' })
366
+ // → WHERE (amount > 1000)
367
+
368
+ // OR condition use an array of objects
369
+ query.table('users').where([{ status: 'active' }, { status: 'pending' }])
370
+ //WHERE ((status = 'active') OR (status = 'pending'))
371
+
372
+ // IS NULL — use literal()
373
+ query.table('users').where({ deleted_at: query.literal('IS NULL') })
374
+ // → WHERE (deleted_at IS NULL)
375
+
376
+ // AND / OR chaining
377
+ const w = query.table('products').where({ category: 'electronics' });
378
+ w.and({ price: '< 500' });
379
+ w.or({ featured: 1 });
380
+ ```
381
+
382
+ ---
383
+
384
+ ### SELECT
385
+
386
+ ```javascript
387
+ // All columns
388
+ const result = await query.table('users').where({ active: 1 }).select().execute();
389
+
390
+ // Specific columns
391
+ const result = await query.table('users').select('id', 'name', 'email').execute();
392
+
393
+ // With ORDER BY / GROUP BY / LIMIT
394
+ const result = await query
395
+ .table('orders')
396
+ .where({ user_id: 42 })
397
+ .order_by('created_at DESC')
398
+ .limit(0, 20)
399
+ .select('id', 'total')
400
+ .execute();
401
+
402
+ // FOR UPDATE (use inside a transaction)
403
+ const result = await query.table('items').where({ id: 1 }).select().for_update().execute();
404
+ ```
405
+
406
+ ---
407
+
408
+ ### INSERT
409
+
410
+ ```javascript
411
+ // Standard INSERT
412
+ const result = await query
413
+ .table('users')
414
+ .insert({ name: 'Alice', email: 'alice@example.com', active: 1 })
415
+ .execute();
416
+ console.log(result.affected); // 1
417
+ console.log(result.insertId); // generated PK
418
+
419
+ // INSERT IGNORE
420
+ const result = await query
421
+ .table('users')
422
+ .insert({ email: 'bob@example.com' }, { ignore: true })
423
+ .execute();
424
+
425
+ // Upsert — ON DUPLICATE KEY UPDATE
426
+ const result = await query
427
+ .table('user_stats')
428
+ .insert({ user_id: 7, login_count: 1 })
429
+ .on('DUPLICATE', { login_count: '= login_count + 1' })
430
+ .execute();
431
+ ```
432
+
433
+ ---
434
+
435
+ ### UPDATE
436
+
437
+ ```javascript
438
+ // Standard UPDATE
439
+ const result = await query
440
+ .table('users')
441
+ .where({ id: 1 })
442
+ .update({ name: 'Bob', email: 'bob@example.com' })
443
+ .execute();
444
+ console.log(result.affected); // number of rows changed
445
+
446
+ // Arithmetic update prefix value with =
447
+ const result = await query
448
+ .table('accounts')
449
+ .where({ id: 1 })
450
+ .update({ balance: '= balance - 100' })
451
+ .execute();
452
+ ```
453
+
454
+ ---
455
+
456
+ ### DELETE
457
+
458
+ ```javascript
459
+ const result = await query
460
+ .table('sessions')
461
+ .where({ user_id: 1 })
462
+ .delete()
463
+ .execute();
464
+ console.log(result.affected);
465
+ // DELETE FROM sessions WHERE (user_id = 1)
466
+ ```
467
+
468
+ > ⚠️ Calling `.delete()` without `.where()` returns an error query that rejects on `execute()`. (Safety guard)
469
+
470
+ ---
471
+
472
+ ### Modifiers
473
+
474
+ | Method | Description |
475
+ |--------|-------------|
476
+ | `.order_by('col ASC')` | ORDER BY |
477
+ | `.group_by('col')` | GROUP BY |
478
+ | `.limit(count)` | Limit number of rows returned |
479
+ | `.limit(offset, count)` | Limit with offset (pagination) |
480
+ | `.for_update()` | Append `FOR UPDATE` to SELECT (transaction only) |
481
+ | `.on(event, fields)` | Set `ON DUPLICATE KEY UPDATE` fields for INSERT |
482
+
483
+ ---
484
+
485
+ ### Sub-queries
486
+
487
+ Pass a `MQQuery` instance directly as a table source. Build the sub-query inline on the same `query` object to avoid creating a separate variable — a separate instance could cause a memory leak since it holds its own connector reference.
488
+
489
+ ```javascript
490
+ const query = await Database.singleton();
491
+
492
+ // Build the sub-query inline on the same query instance
493
+ const result = await query
494
+ .table('users', 'u')
495
+ .inner(
496
+ query.table('orders').where({ status: 'paid' }).select('user_id', 'SUM(total) AS revenue'),
497
+ 'o',
498
+ { 'u.id': 'o.user_id' }
499
+ )
500
+ .select('u.name', 'o.revenue')
501
+ .execute();
502
+
503
+ console.log(result.rows);
504
+ ```
505
+
506
+ > ⚠️ Do **not** create a separate `sub` instance for sub-queries.
507
+ > A separate `MQQuery` instance retains its own connector reference and may cause a memory leak if not properly closed.
508
+ > Always compose sub-queries using the same `query` object.
509
+
510
+ ---
511
+
512
+ ## ResultSet
513
+
514
+ When `execute()` resolves it returns a **ResultSet object**.
515
+ The available fields depend on the type of query.
516
+
517
+ ### SELECT `result.rows`
518
+
519
+ ```javascript
520
+ const result = await query.table('tbl_student').where({ stdid: 10 }).select().execute();
521
+
522
+ result.rows; // full array of row objects
523
+ result.rows[0]; // first row
524
+ result.rows[0].schid; // column value
525
+ result.rows.length; // row count
526
+
527
+ // Example output:
528
+ // [
529
+ // { stdid: 10, name: 'Alice', schid: 1 },
530
+ // ...
531
+ // ]
532
+ ```
533
+
534
+ Handling empty results:
535
+
536
+ ```javascript
537
+ const result = await query.table('users').where({ id: 9999 }).select().execute();
538
+ if (!result.rows || result.rows.length === 0) {
539
+ console.log('Not found');
540
+ } else {
541
+ console.log(result.rows[0].name);
542
+ }
543
+ ```
544
+
545
+ ---
546
+
547
+ ### INSERT / UPDATE / DELETE — `result.affected`
548
+
549
+ ```javascript
550
+ // UPDATE
551
+ const result = await query
552
+ .table('tbl_student')
553
+ .where({ stdid: 10 })
554
+ .update({ schid: 10 })
555
+ .execute();
556
+
557
+ result.affected; // number of rows changed
558
+
559
+ // INSERT
560
+ const result = await query
561
+ .table('users')
562
+ .insert({ name: 'Alice' })
563
+ .execute();
564
+
565
+ result.affected; // number of rows inserted
566
+ result.insertId; // generated auto-increment PK
567
+ ```
568
+
569
+ ---
570
+
571
+ ### ResultSet Field Reference
572
+
573
+ | Field | Query Type | Description |
574
+ |-------|------------|-------------|
575
+ | `rows` | SELECT | Array of row objects. Empty result returns `[]`. |
576
+ | `rows[n]` | SELECT | The nth row object. Column names are keys. |
577
+ | `affected` | INSERT / UPDATE / DELETE | Number of rows affected |
578
+ | `insertId` | INSERT | Auto-increment PK of the inserted row. `0` if no AI column. |
579
+
580
+ ---
581
+
582
+ ### Real-world Pattern — ResultSet inside a transaction
583
+
584
+ ```javascript
585
+ const query = await Database.transaction('example', 'master');
586
+
587
+ let result;
588
+
589
+ // SELECT → read rows
590
+ result = await query.table('tbl_student').where({ stdid: 10 }).select().execute();
591
+ console.log('select1:', result.rows); // full array
592
+ console.log('schid:', result.rows[0].schid); // column access
593
+
594
+ // UPDATE → check affected
595
+ result = await query.table('tbl_student').where({ stdid: 10 }).update({ schid: 10 }).execute();
596
+ console.log('update1 affected:', result.affected);
597
+
598
+ // SELECT again to verify the change
599
+ result = await query.table('tbl_student').where({ stdid: 10 }).select().execute();
600
+ console.log('select2 schid:', result.rows[0].schid); // 10
601
+
602
+ // Revert
603
+ result = await query.table('tbl_student').where({ stdid: 10 }).update({ schid: 1 }).execute();
604
+ console.log('update2 affected:', result.affected);
605
+
606
+ result = await query.table('tbl_student').where({ stdid: 10 }).select().execute();
607
+ console.log('select3 schid:', result.rows[0].schid); // 1
608
+
609
+ await query.commit();
610
+ ```
611
+
612
+ ---
613
+
614
+ ## Oracle Driver
615
+
616
+ Driver identifier: `'oracle'` (uses the `oracledb` package internally)
617
+
618
+ ### Installation
619
+
620
+ ```bash
621
+ npm install oracledb
622
+
623
+ # For Thick mode, install Oracle Instant Client then call:
624
+ oracledb.initOracleClient({ libDir: '/opt/oracle/instantclient_21_9' });
625
+ ```
626
+
627
+ > **Thin vs Thick Mode**
628
+ > oracledb v6+ supports a pure-JS Thin mode that does not require Oracle Instant Client.
629
+ > Use Thick mode only when you need advanced features such as Advanced Queuing or LDAP authentication.
630
+
631
+ ---
632
+
633
+ ### Connection Option
634
+
635
+ Oracle uses the `database` field for an Easy Connect string or TNS alias instead of a `host`/`port` pair.
636
+
637
+ ```javascript
638
+ // Easy Connect
639
+ const option = {
640
+ alias: 'main',
641
+ user: 'hr',
642
+ password: 'oracle',
643
+ connectString: 'localhost/XEPDB1', // <host>/<service_name>
644
+ poolMax: 10,
645
+ poolMin: 0,
646
+ poolIncrement: 1,
647
+ poolTimeout: 30,
648
+ poolPingInterval: 10,
649
+ };
650
+
651
+ // TNS alias (requires tnsnames.ora or TNS_ADMIN env var)
652
+ const option = {
653
+ alias: 'prod',
654
+ user: 'app_user',
655
+ password: 'secret',
656
+ connectString: 'PROD_DB',
657
+ };
658
+ ```
659
+
660
+ ---
661
+
662
+ ### Usage Example
663
+
664
+ ```javascript
665
+ const qz = new Querize('oracle');
666
+ await qz.initialize({ libDir: '/oracle' }); // omit for Thin mode
667
+ const Database = await qz.createPool({
668
+ alias: 'main', user: 'hr', password: 'oracle',
669
+ connectString: 'localhost/XEPDB1', poolMax: 10,
670
+ });
671
+
672
+ const query = await Database.singleton();
673
+ const result = await query.table('EMPLOYEES').where({ DEPARTMENT_ID: 90 }).select().execute();
674
+ console.log(result.rows);
675
+ ```
676
+
677
+ ---
678
+
679
+ ### Oracle-specific Notes
680
+
681
+ **Table and column names are UPPERCASE by default** (Oracle default behavior)
682
+
683
+ ```javascript
684
+ query.table('EMPLOYEES').where({ DEPARTMENT_ID: 10 }).select('FIRST_NAME', 'SALARY')
685
+ ```
686
+
687
+ **DUAL table**
688
+
689
+ ```javascript
690
+ const result = await query.table('DUAL').select('SYSDATE').execute();
691
+ // → SELECT SYSDATE FROM DUAL
692
+ ```
693
+
694
+ **Pagination** — `.limit()` is not supported in Oracle; use raw SQL
695
+
696
+ ```javascript
697
+ // Oracle 12c+
698
+ const result = await Database.query(
699
+ 'SELECT * FROM EMPLOYEES ORDER BY EMPLOYEE_ID FETCH FIRST 20 ROWS ONLY'
700
+ );
701
+
702
+ // Oracle 11g and below
703
+ const result = await Database.query(
704
+ 'SELECT * FROM (SELECT * FROM EMPLOYEES ORDER BY EMPLOYEE_ID) WHERE ROWNUM <= 20'
705
+ );
706
+ ```
707
+
708
+ **SEQUENCE (auto-increment substitute)**
709
+
710
+ ```javascript
711
+ const result = await query.table('ORDERS').insert({
712
+ ORDER_ID: query.literal('ORDER_SEQ.NEXTVAL'),
713
+ STATUS: 'NEW',
714
+ USER_ID: 42,
715
+ }).execute();
716
+ ```
717
+
718
+ **Oracle ResultSet field differences**
719
+
720
+ | Field | Description |
721
+ |-------|-------------|
722
+ | `rows` | SELECT result array (same shape as mysql2) |
723
+ | `affected` | INSERT/UPDATE/DELETE affected row count (mapped from `rowsAffected`) |
724
+ | `insertId` | Oracle ROWID string of the inserted row (mapped from `lastRowid`) |
725
+
726
+ ---
727
+
728
+ ## Trace / Logging
729
+
730
+ ```javascript
731
+ import { Querize } from 'querize';
732
+
733
+ Querize.setTrace((level, tag, msg) => {
734
+ console.log(`[${level}] ${tag}: ${msg}`);
735
+ });
736
+ // level: 'log' | 'err' | 'sql'
737
+ ```
738
+
739
+ ---
740
+
741
+ ## Connection Mode Summary
742
+
743
+ | Mode | Connection Handling | Best For |
744
+ |------|---------------------|----------|
745
+ | `createConnect` | New TCP connection per operation | Scripts, CLIs, migrations |
746
+ | `createPool` | Reusable connection pool | Web servers, APIs (recommended default) |
747
+ | `createCluster` | Multiple pools as one object | Read/write splitting, HA setups |
748
+ | `transaction()` | Dedicated connection + BEGIN | Multi-statement atomicity |
749
+ | `singleton()` | Acquire → execute → release per call | Independent single queries |
750
+
751
+ ---
752
+
753
+ ## License
754
+
755
+ MIT © 2020 lClasser — [Querize](https://github.com/itfin-git/Querize)