@areumtecnologia/mysql-db-handler 1.0.7 → 1.0.9

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 CHANGED
@@ -1,14 +1,48 @@
1
1
  # @areumtecnologia/mysql-db-handler
2
2
 
3
- A powerful MySQL wrapper with connection pooling, singleton support, and advanced query handling for Node.js.
3
+ A powerful, robust, and production-ready MySQL wrapper for Node.js. It features connection pooling, query caching (optional), jQuery DataTables server-side processing integration, safe error handling, and a flexible abstraction layer for CRUD operations.
4
+
5
+ ## Table of Contents
6
+
7
+ * [Features](#features)
8
+ * [Installation](#installation)
9
+ * [Core Classes](#core-classes)
10
+ * [DataBase (Standard Connection)](#1-database-standard-connection)
11
+ * [DataBase (With Query & Schema Caching)](#2-database-with-query--schema-caching)
12
+ * [DataBaseHandler (CRUD Abstraction)](#3-databasehandler-crud-abstraction)
13
+ * [Basic Usage](#basic-usage)
14
+ * [1. Initializing Connection](#1-initializing-connection)
15
+ * [2. Executing Raw SQL](#2-executing-raw-sql)
16
+ * [3. Using the DataBaseHandler](#3-using-the-databasehandler)
17
+ * [Advanced Querying (`select` method)](#advanced-querying-select-method)
18
+ * [Basic Selection](#basic-selection)
19
+ * [Working with the `IN` and `NOT IN` Operators](#working-with-the-in-and-not-in-operators)
20
+ * [Automatic `IN` Detection](#automatic-in-detection)
21
+ * [Explicit `IN` / `NOT IN` Operator](#explicit-in--not-in-operator)
22
+ * [Empty Array Safety (`IN ()` Protection)](#empty-array-safety-in-protection)
23
+ * [Combining Multiple Conditions (AND / OR)](#combining-multiple-conditions-and--or)
24
+ * [CRUD Operations](#crud-operations)
25
+ * [Inserting Records (`insert`)](#inserting-records-insert)
26
+ * [Updating Records (`update`)](#updating-records-update)
27
+ * [Deleting Records (`delete`)](#deleting-records-delete)
28
+ * [jQuery DataTables Server-Side Support](#jquery-datatables-server-side-support)
29
+ * [Database Singleton Pattern](#database-singleton-pattern)
30
+ * [Error Handling Strategy](#error-handling-strategy)
31
+ * [Contributing](#contributing)
32
+ * [License](#license)
33
+
34
+ ---
4
35
 
5
36
  ## Features
6
37
 
7
- * **Connection Pooling:** Efficiently manages database connections.
8
- * **Promise-based:** Fully supports `async/await` for clean code.
9
- * **Table Handler:** An abstraction layer to perform CRUD operations easily.
10
- * **Datatables Integration:** Built-in helper to parse and query data specifically for jQuery Datatables server-side processing.
11
- * **Singleton Support:** Includes a singleton pattern setup for easy global access.
38
+ * **Connection Pooling:** Efficiently manages and recycles database connections using `mysql2/promise`.
39
+ * **Flexible Query Builder:** Run complex operations without writing manual SQL strings.
40
+ * **Automatic `IN` Handling:** Smart processing of arrays for SQL `IN` and `NOT IN` operations (including prevention of empty array query crashes).
41
+ * **Optional Caching Wrapper:** Native support for query and schema caching using `node-cache` to boost performance.
42
+ * **Server-Side DataTables Integration:** Effortless integration with jQuery DataTables server-side requests, handling filters, sorting, search (global and column-based), regex, and count totals automatically.
43
+ * **Robust Error Handling:** Intercepts and wraps database errors in predictable objects to prevent application crashes.
44
+
45
+ ---
12
46
 
13
47
  ## Installation
14
48
 
@@ -16,206 +50,366 @@ A powerful MySQL wrapper with connection pooling, singleton support, and advance
16
50
  npm install @areumtecnologia/mysql-db-handler
17
51
  ```
18
52
 
53
+ If you wish to use the caching features, ensure `node-cache` is installed:
54
+ ```bash
55
+ npm install node-cache
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Core Classes
61
+
62
+ The package exports the following primary components:
63
+
64
+ ### 1. `DataBase` (Standard Connection)
65
+ Located in `lib/database.js`. Manages standard pooled connections, schema discovery, and query execution.
66
+
67
+ ### 2. `DataBase` (With Query & Schema Caching)
68
+ Located in `lib/database-cache.js`. Overrides the standard `DataBase` implementation to provide in-memory query and table schema caching. It is ideal for read-heavy environments.
69
+
70
+ ### 3. `DataBaseHandler` (CRUD Abstraction)
71
+ Located in `lib/databasehandler.js`. Bind this class to a specific table to perform database actions through utility methods (`select`, `selectBy`, `insert`, `update`, `delete`, `selectToDatatable`).
72
+
73
+ ---
74
+
19
75
  ## Basic Usage
20
76
 
21
- ### 1. Initialize Connection
22
- You can create a new instance of the database class directly.
77
+ ### 1. Initializing Connection
23
78
 
24
79
  ```javascript
25
80
  const { DataBase } = require('@areumtecnologia/mysql-db-handler');
26
81
 
82
+ // Configuration matches mysql2 connection configuration
27
83
  const db = new DataBase({
28
84
  host: 'localhost',
29
85
  user: 'root',
30
86
  password: 'password',
31
87
  database: 'my_app_db',
32
88
  port: 3306,
33
- connectionLimit: 10
89
+ connectionLimit: 10,
90
+ waitForConnections: true,
91
+ queueLimit: 0
34
92
  });
35
93
  ```
36
94
 
37
95
  ### 2. Executing Raw SQL
38
- Use the `query` method to execute standard SQL.
96
+
97
+ Raw queries are executed through connection pooling. The library handles obtaining and releasing the connection automatically.
39
98
 
40
99
  ```javascript
41
100
  try {
42
- const rows = await db.query('SELECT * FROM users WHERE active = ?', [1]);
43
- console.log(rows);
101
+ const rows = await db.query('SELECT * FROM users WHERE active = ? AND role = ?', [1, 'editor']);
102
+ console.log('Active editors:', rows);
44
103
  } catch (error) {
45
- console.error(error);
104
+ console.error('Database query failed:', error);
46
105
  }
47
106
  ```
48
107
 
49
108
  ### 3. Using the DataBaseHandler
50
- The `DataBaseHandler` simplifies interactions with specific tables.
109
+
110
+ Wrap a table with a handler to abstract SQL construction.
51
111
 
52
112
  ```javascript
53
- const { DataBaseHandler } = require('@areumtecnologia/mysql-db-handler');
113
+ const { DataBase, DataBaseHandler } = require('@areumtecnologia/mysql-db-handler');
54
114
 
55
- // Initialize handler for 'customers' table
56
- const customers = new DataBaseHandler(db, 'customers');
115
+ const db = new DataBase({ /* config */ });
116
+ const usersHandler = new DataBaseHandler(db, 'users');
57
117
  ```
58
118
 
59
119
  ---
60
120
 
61
- ## Advanced Usage & Examples
62
-
63
- ### Inserting Records (`insert`)
121
+ ## Advanced Querying (`select` method)
64
122
 
65
- The `insert` method adds a new row to the table.
123
+ The `select(params, clauses)` method builds complex SQL `WHERE` clauses from arrays of condition objects.
66
124
 
67
- **Parameters:**
68
- * `params` (Object): An object where keys match the column names and values are the data to insert.
125
+ ### Basic Selection
69
126
 
70
- **Example:**
127
+ You can pass condition objects where the key is the column name and the value is the target value.
71
128
 
72
129
  ```javascript
73
- const logs = new DataBaseHandler(db, 'app_logs');
130
+ // Simple equals comparison (=)
131
+ const users = await usersHandler.select([
132
+ { role: 'admin' }
133
+ ]);
134
+ // Executes: SELECT * FROM users WHERE `role` = ?
135
+ ```
74
136
 
75
- const result = await logs.insert({
76
- level: 'info',
77
- message: 'User logged in',
78
- timestamp: new Date()
79
- });
80
- // Executes: INSERT INTO app_logs SET `level` = ?, `message` = ?, `timestamp` = ?
81
- console.log(`Inserted Row ID: ${result.insertId}`);
137
+ ### Working with the `IN` and `NOT IN` Operators
138
+
139
+ The library provides special support for SQL `IN` operations by mapping JavaScript arrays to queries.
140
+
141
+ #### Automatic `IN` Detection
142
+ If a condition's value is an array, and no operator is explicitly set, the query builder automatically treats it as an `IN` statement:
143
+
144
+ ```javascript
145
+ const activeUsers = await usersHandler.select([
146
+ { status: ['active', 'pending'] }
147
+ ]);
148
+ // Executes: SELECT * FROM users WHERE `status` IN (?, ?)
149
+ // Bindings: ['active', 'pending']
82
150
  ```
83
151
 
84
- ### Flexible Selection (`select`)
152
+ #### Explicit `IN` / `NOT IN` Operator
153
+ You can explicitly define the `operator` as `'IN'` or `'NOT IN'`:
85
154
 
86
- The `select` method allows you to build complex queries using an array of parameters and a clauses object.
155
+ ```javascript
156
+ // Using NOT IN explicitly
157
+ const nonAdminUsers = await usersHandler.select([
158
+ { role: ['admin', 'superadmin'], operator: 'NOT IN' }
159
+ ]);
160
+ // Executes: SELECT * FROM users WHERE `role` NOT IN (?, ?)
161
+ // Bindings: ['admin', 'superadmin']
162
+ ```
87
163
 
88
- **Parameters:**
89
- * `params` (Array): An array where each element is either a condition object or a logical string ('AND', 'OR').
90
- * Simple condition: `{ column: value }` (defaults to `=`)
91
- * Condition with operator: `{ column: value, operator: '>' }`
92
- * `clauses` (Object): Optional keys like `ORDERBY`, `DESC`, `LIMIT`, `OFFSET`, `GROUP BY`, `HAVING`.
164
+ #### Empty Array Safety (`IN ()` Protection)
165
+ In standard SQL, executing a query with an empty array in the `IN` clause (e.g., `WHERE status IN ()`) yields a syntax error and crashes the execution. The library automatically mitigates this:
93
166
 
94
- **Example:**
167
+ * **Empty `IN`**: Evaluates to `1 = 0` (always false). No records match this sub-clause, ensuring safe execution.
168
+ * **Empty `NOT IN`**: Evaluates to `1 = 1` (always true). All records match this sub-clause, returning all values since nothing is excluded.
95
169
 
96
170
  ```javascript
97
- const products = new DataBaseHandler(db, 'products');
171
+ // Safe empty array handling
172
+ const results = await usersHandler.select([
173
+ { id: [], operator: 'IN' }
174
+ ]);
175
+ // Executes: SELECT * FROM users WHERE 1 = 0
176
+
177
+ const allResults = await usersHandler.select([
178
+ { id: [], operator: 'NOT IN' }
179
+ ]);
180
+ // Executes: SELECT * FROM users WHERE 1 = 1
181
+ ```
98
182
 
99
- const results = await products.select(
100
- // 1. Params: Conditions
183
+ ### Combining Multiple Conditions (AND / OR)
184
+
185
+ Interleave condition objects with string elements representing logical operators:
186
+
187
+ ```javascript
188
+ const filteredProducts = await productsHandler.select(
101
189
  [
102
- { category: 'electronics' }, // WHERE `category` = 'electronics'
190
+ { category: 'electronics' },
103
191
  'AND',
104
- { price: 1000, operator: '>' } // AND `price` > 1000
192
+ { price: 500, operator: '>' },
193
+ 'AND',
194
+ { brand: ['Sony', 'LG'] } // Automatically mapped to IN
105
195
  ],
106
- // 2. Clauses: SQL Modifiers
107
196
  {
108
197
  'ORDERBY': 'price',
109
198
  'DESC': true,
110
- 'LIMIT': 10,
111
- 'OFFSET': 0
199
+ 'LIMIT': 20
112
200
  }
113
201
  );
202
+ // Executes:
203
+ // SELECT * FROM products WHERE `category` = ? AND `price` > ? AND `brand` IN (?, ?) ORDER BY price DESC LIMIT 20
114
204
  ```
115
205
 
116
- ### Updating Records (`update`)
206
+ ---
117
207
 
118
- The `update` method separates the values to change from the conditions.
208
+ ## CRUD Operations
119
209
 
120
- **Parameters:**
121
- * `params` (Object): Contains:
122
- * `set`: Key-value pairs of columns to update.
123
- * `where`: Key-value pairs for the WHERE clause (uses `AND` implicitly between keys).
124
- * `options` (Object): Optional settings, e.g., `{ useRegex: true }` to use REGEXP instead of =.
210
+ ### Inserting Records (`insert`)
125
211
 
126
- **Example:**
212
+ Adds a new row to the associated table using key-value properties.
127
213
 
128
214
  ```javascript
129
- const users = new DataBaseHandler(db, 'users');
215
+ const result = await usersHandler.insert({
216
+ name: 'John Doe',
217
+ email: 'john.doe@example.com',
218
+ role: 'member',
219
+ created_at: new Date()
220
+ });
221
+
222
+ console.log('Inserted ID:', result.insertId);
223
+ ```
224
+
225
+ ### Updating Records (`update`)
130
226
 
131
- const result = await users.update({
227
+ Performs selective updates by specifying both a `set` payload and a `where` filter. The `where` filter supports both standard flat objects (where conditions are implicitly joined by `AND` using `=` or `REGEXP`) and complex array configurations (with operators, `AND`, and `OR`), sharing the same advanced querying capabilities as the `select` method.
228
+
229
+ #### 1. Simple Object Syntax (Implicit AND, = operator)
230
+
231
+ ```javascript
232
+ const result = await usersHandler.update({
132
233
  set: {
133
- status: 'inactive',
134
- last_seen: new Date()
234
+ status: 'suspended',
235
+ notes: 'Violated terms of service'
135
236
  },
136
237
  where: {
137
- id: 123,
138
- role: 'guest'
238
+ id: 42,
239
+ status: 'active'
139
240
  }
140
241
  });
141
- // Executes: UPDATE users SET `status` = ?, `last_seen` = ? WHERE `id` = ? AND `role` = ?
242
+
243
+ console.log('Affected rows:', result.affectedRows);
244
+ // Executes: UPDATE users SET `status` = ?, `notes` = ? WHERE `id` = ? AND `status` = ?
245
+ ```
246
+
247
+ #### 2. Advanced Array Syntax (with operators, AND/OR, and IN)
248
+
249
+ You can pass an array of condition objects interleaved with `AND` or `OR` string elements to construct complex queries:
250
+
251
+ ```javascript
252
+ // Example updating records based on numeric thresholds and logical operators
253
+ const result = await usersHandler.update({
254
+ set: {
255
+ status: 'inactive'
256
+ },
257
+ where: [
258
+ { age: 18, operator: '<' },
259
+ 'AND',
260
+ { status: 'active' }
261
+ ]
262
+ });
263
+ // Executes: UPDATE users SET `status` = ? WHERE `age` < ? AND `status` = ?
264
+
265
+ // Example updating records using automatic IN operator detection
266
+ const result = await usersHandler.update({
267
+ set: {
268
+ role: 'archived'
269
+ },
270
+ where: [
271
+ { id: [10, 20, 30] } // Automatically builds: WHERE `id` IN (?, ?, ?)
272
+ ]
273
+ });
274
+ // Executes: UPDATE users SET `role` = ? WHERE `id` IN (?, ?, ?)
275
+
276
+ // Example combining complex logic (AND/OR)
277
+ const result = await usersHandler.update({
278
+ set: {
279
+ category: 'Premium'
280
+ },
281
+ where: [
282
+ { total_purchases: 1000, operator: '>' },
283
+ 'AND',
284
+ { status: 'active' },
285
+ 'OR',
286
+ { is_vip: 1 }
287
+ ]
288
+ });
289
+ // Executes: UPDATE users SET `category` = ? WHERE `total_purchases` > ? AND `status` = ? OR `is_vip` = ?
142
290
  ```
143
291
 
144
292
  ### Deleting Records (`delete`)
145
293
 
146
- The `delete` method removes rows matching the provided conditions.
294
+ Deletes rows matching the conditions object (joined by implicit `AND` operators).
295
+
296
+ ```javascript
297
+ const result = await usersHandler.delete({
298
+ status: 'temporary',
299
+ is_expired: 1
300
+ });
147
301
 
148
- **Parameters:**
149
- * `conditions` (Object): Key-value pairs to match rows to delete (uses `AND` implicitly).
302
+ console.log('Deleted rows:', result.affectedRows);
303
+ ```
304
+
305
+ ---
150
306
 
151
- **Example:**
307
+ ## jQuery DataTables Server-Side Support
308
+
309
+ The `selectToDatatable` method processes the complex nested payloads sent by jQuery DataTables server-side scripts and returns the required format.
152
310
 
153
311
  ```javascript
154
- const sessions = new DataBaseHandler(db, 'user_sessions');
312
+ // Express.js Route Example
313
+ app.post('/api/users/dt', async (req, res) => {
314
+ // req.body contains the DataTables standard payload:
315
+ // { draw: 1, start: 0, length: 10, search: { value: 'John', regex: false }, columns: [...] }
316
+
317
+ // You can enforce strict conditions that the client cannot bypass (e.g., tenant containment)
318
+ const strictConditions = [
319
+ { tenant_id: req.user.tenantId },
320
+ 'AND',
321
+ { status: 'deleted', operator: '!=' }
322
+ ];
155
323
 
156
- const result = await sessions.delete({
157
- user_id: 55,
158
- is_expired: 1
324
+ const datatableResult = await usersHandler.selectToDatatable(req.body, strictConditions);
325
+
326
+ // Returns: { draw: 1, recordsTotal: 100, recordsFiltered: 12, data: [...] }
327
+ res.json(datatableResult);
159
328
  });
160
- // Executes: DELETE FROM user_sessions WHERE `user_id` = ? AND `is_expired` = ?
161
329
  ```
162
330
 
163
331
  ---
164
332
 
165
- ## API Documentation
333
+ ## Database Singleton Pattern
334
+
335
+ To prevent initializing redundant pool connections across various modules in a Node.js project, it is highly recommended to configure a single shared instance (Singleton).
336
+
337
+ Create a `db.js` file:
338
+ ```javascript
339
+ // db.js
340
+ const { DataBase } = require('@areumtecnologia/mysql-db-handler');
166
341
 
167
- ### Class: `DataBase`
342
+ const dbInstance = new DataBase({
343
+ host: process.env.DB_HOST || 'localhost',
344
+ user: process.env.DB_USER || 'root',
345
+ password: process.env.DB_PASSWORD,
346
+ database: process.env.DB_NAME,
347
+ connectionLimit: parseInt(process.env.DB_POOL_LIMIT) || 10
348
+ });
168
349
 
169
- #### `constructor(config)`
170
- Creates a database instance with a connection pool.
171
- * **config**: Object. MySQL2 connection configuration (host, user, password, database, port, connectionLimit, etc).
350
+ module.exports = dbInstance;
351
+ ```
172
352
 
173
- #### `async query(sql, params)`
174
- Executes a SQL query using a pooled connection.
175
- * **sql**: String. The SQL query to execute.
176
- * **params**: Array. Parameters for the prepared statement.
177
- * **Returns**: `Promise<Array|Object>`. Returns rows on success, or an error object if failed.
353
+ Import it in your business controllers/repositories:
354
+ ```javascript
355
+ // usersRepository.js
356
+ const db = require('./db');
357
+ const { DataBaseHandler } = require('@areumtecnologia/mysql-db-handler');
178
358
 
179
- #### `async close()`
180
- Closes the connection pool completely.
181
- * **Returns**: `Promise<void>`.
359
+ const usersHandler = new DataBaseHandler(db, 'users');
360
+
361
+ module.exports = {
362
+ getUserById: (id) => usersHandler.selectBy({ id })
363
+ };
364
+ ```
182
365
 
183
366
  ---
184
367
 
185
- ### Class: `DataBaseHandler`
368
+ ## Error Handling Strategy
186
369
 
187
- #### `constructor(database, table, expression)`
188
- * **database**: Instance of `DataBase`.
189
- * **table**: String. The name of the table.
190
- * **expression**: String (Optional). Additional SQL expression (e.g., extra columns or joins).
370
+ Database exceptions generated during execution are caught internally by the `DataBase` instance. This means that instead of raising unhandled exceptions that could crash your Node.js application, the library wraps the error.
191
371
 
192
- #### `async select(params, clauses)`
193
- See "Flexible Selection" above.
194
- * **Returns**: `Promise<Array>`.
372
+ Specifically, the following methods from **`DataBase`** and **`DataBaseHandler`** return an object with an `error` property when an execution error occurs:
373
+ * `DataBase.query()`
374
+ * `DataBaseHandler.select()` / `selectBy()`
375
+ * `DataBaseHandler.insert()`
376
+ * `DataBaseHandler.update()`
377
+ * `DataBaseHandler.delete()`
378
+ * `DataBaseHandler.selectToDatatable()`
195
379
 
196
- #### `async selectBy(params)`
197
- Quickly select rows matching a specific object condition.
198
- * **params**: Object. Key-value pairs for WHERE clause (e.g., `{ id: 5 }`).
199
- * **Returns**: `Promise<Array>`. (contains rows array from query)
380
+ ### Example: Checking for errors in queries
200
381
 
201
- #### `async insert(params)`
202
- Inserts a new row.
203
- * **params**: Object. Key-value pairs representing column names and values.
204
- * **Returns**: `Promise<OkPacket>` (contains insertId, or error).
382
+ ```javascript
383
+ // Example with Select if column doesn't exist (triggers database error)
384
+ const users = await usersHandler.select([{ non_existent_column: 'admin' }]);
385
+ if (users.error) {
386
+ console.error('Failed to select users:', users.error.message);
387
+ }
205
388
 
206
- #### `async update(params, options)`
207
- See "Updating Records" above.
208
- * **Returns**: `Promise<OkPacket>`. (contains affectedRows or error).
389
+ // Example with Insert
390
+ const insertResult = await usersHandler.insert({ name: 'John Doe' });
391
+ if (insertResult.error) {
392
+ console.error('Failed to insert user:', insertResult.error.message);
393
+ }
209
394
 
210
- #### `async delete(conditions)`
211
- See "Deleting Records" above.
212
- * **Returns**: `Promise<OkPacket>`. (contains affectedRows or error)
395
+ // Example with Update
396
+ const updateResult = await usersHandler.update({
397
+ set: { status: 'active' },
398
+ where: { id: 999 }
399
+ });
400
+ if (updateResult.error) {
401
+ console.error('Failed to update user:', updateResult.error.message);
402
+ }
213
403
 
214
- #### `async selectToDatatable(rawDtQuery, strictCondition)`
215
- Helper for server-side Datatables.net processing.
216
- * **rawDtQuery**: Object. The request object sent by Datatables.
217
- * **strictCondition**: Array. Additional strict WHERE clauses not controlled by the frontend.
218
- * **Returns**: Object. Format expected by Datatables `{ draw, recordsTotal, recordsFiltered, data }`.
404
+ // Example with Delete
405
+ const deleteResult = await usersHandler.delete({ id: 999 });
406
+ if (deleteResult.error) {
407
+ console.error('Failed to delete user:', deleteResult.error.message);
408
+ }
409
+ ```
410
+
411
+ > [!NOTE]
412
+ > If you execute a `select` using valid columns but search for values that do not exist in the table (e.g. `await usersHandler.select([{ role: 'non_existent_role' }])`), this is **not** considered a database error. The query will execute successfully and return an empty array `[]` (without any `error` property). An error is only returned for actual database execution failures (such as syntax errors, missing columns, database connection drops, etc.).
219
413
 
220
414
  ---
221
415
 
@@ -223,12 +417,14 @@ Helper for server-side Datatables.net processing.
223
417
 
224
418
  Contributions are welcome! Please open an issue or submit a pull request.
225
419
 
226
- 1. Fork the repository
227
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
228
- 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
229
- 4. Push to the branch (`git push origin feature/amazing-feature`)
230
- 5. Open a Pull Request
420
+ 1. Fork the repository
421
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
422
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
423
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
424
+ 5. Open a Pull Request
425
+
426
+ ---
231
427
 
232
428
  ## License
233
429
 
234
- MIT
430
+ MIT
@@ -62,6 +62,67 @@ class DatabaseHandler {
62
62
  return { sql: `(${clauses.join(' ')})`, bindings };
63
63
  }
64
64
 
65
+ /**
66
+ * @private
67
+ * Constrói a cláusula WHERE (sem a palavra-chave WHERE) e seus bindings associados.
68
+ * @param {Object|Array} where - Condições do where.
69
+ * @param {boolean} useRegex - Se deve usar REGEXP por padrão (para compatibilidade com update).
70
+ * @returns {{ sql: string, bindings: Array }}
71
+ */
72
+ _buildWhereClause(where, useRegex = false) {
73
+ if (!where) {
74
+ return { sql: '', bindings: [] };
75
+ }
76
+
77
+ const conditions = [];
78
+ const bindings = [];
79
+
80
+ if (Array.isArray(where)) {
81
+ where.forEach((param) => {
82
+ if (typeof param === 'object' && param !== null && !Array.isArray(param)) {
83
+ const entries = Object.entries(param);
84
+ if (entries.length === 0) return;
85
+
86
+ const [key, value] = entries[0];
87
+ const [operatorKey, operatorValue] = entries[1] || [];
88
+
89
+ let op = operatorValue ?? (useRegex ? 'REGEXP' : '=');
90
+ let upperOp = String(op).toUpperCase();
91
+
92
+ if (Array.isArray(value) && upperOp === '=') {
93
+ op = 'IN';
94
+ upperOp = 'IN';
95
+ }
96
+
97
+ if (Array.isArray(value) && (upperOp === 'IN' || upperOp === 'NOT IN')) {
98
+ if (value.length === 0) {
99
+ conditions.push(upperOp === 'IN' ? '1 = 0' : '1 = 1');
100
+ } else {
101
+ const placeholders = value.map(() => '?').join(', ');
102
+ conditions.push(`\`${key}\` ${op} (${placeholders})`);
103
+ bindings.push(...value);
104
+ }
105
+ } else {
106
+ const condition = `\`${key}\` ${op} ?`;
107
+ conditions.push(condition);
108
+ bindings.push(value);
109
+ }
110
+ } else if (typeof param === 'string') {
111
+ conditions.push(param.toUpperCase());
112
+ }
113
+ });
114
+ return { sql: conditions.join(' '), bindings };
115
+ } else if (typeof where === 'object' && where !== null) {
116
+ const operator = useRegex ? 'REGEXP' : '=';
117
+ const keys = Object.keys(where);
118
+ const sql = keys.map(key => `\`${key}\` ${operator} ?`).join(' AND ');
119
+ const bindings = Object.values(where);
120
+ return { sql, bindings };
121
+ }
122
+
123
+ return { sql: '', bindings: [] };
124
+ }
125
+
65
126
  async selectToDatatable(rawDtQuery, strictCondition = []) {
66
127
  // NOVO: Hidrata o objeto da requisição para o formato aninhado correto.
67
128
  const dtQuery = this._hydrateObject(rawDtQuery);
@@ -214,61 +275,8 @@ class DatabaseHandler {
214
275
  return new Error('O parâmetro "params" deve ser um array');
215
276
  }
216
277
 
217
- const conditions = [];
218
- const values = [];
219
-
220
- if (params && params.length) {
221
- // Dica: Troquei map por forEach. O map é para transformar arrays,
222
- // como você só está preenchendo variáveis externas, forEach é semanticamente correto.
223
- params.forEach((param) => {
224
- if (typeof param === 'object' && !Array.isArray(param)) {
225
- const entries = Object.entries(param);
226
- const [key, value] = entries[0];
227
- const [operatorKey, operatorValue] = entries[1] || [];
228
-
229
- // --- INÍCIO DAS MODIFICAÇÕES PARA SUPORTE AO IN ---
230
-
231
- // 1. Define o operador (padrão é '=')
232
- let op = operatorValue ?? '=';
233
- const upperOp = op.toUpperCase();
234
-
235
- // 2. "Magia": Se o valor for um array e não tiver operador (ou for '='),
236
- // converte automaticamente para 'IN' para facilitar sua vida.
237
- if (Array.isArray(value) && upperOp === '=') {
238
- op = 'IN';
239
- }
240
-
241
- // 3. Lógica específica para IN e NOT IN
242
- if (Array.isArray(value) && (upperOp === 'IN' || upperOp === 'NOT IN')) {
243
- if (value.length === 0) {
244
- // O SQL não permite "IN ()".
245
- // Um IN vazio nunca encontra nada (falso), um NOT IN vazio encontra tudo (verdadeiro).
246
- conditions.push(upperOp === 'IN' ? '1 = 0' : '1 = 1');
247
- } else {
248
- // Cria a string de placeholders: "?, ?, ?" baseada no tamanho do array
249
- const placeholders = value.map(() => '?').join(', ');
250
- conditions.push(`\`${key}\` ${op} (${placeholders})`);
251
-
252
- // Espalha os valores do array individualmente no array de values
253
- values.push(...value);
254
- }
255
- } else {
256
- // 4. Lógica original para valores únicos (string, number, boolean, etc)
257
- // ou outros operadores (LIKE, REGEXP, >, <, etc)
258
- const condition = `\`${key}\` ${op} ?`;
259
- conditions.push(condition);
260
- values.push(value);
261
- }
262
-
263
- // --- FIM DAS MODIFICAÇÕES ---
264
-
265
- } else if (typeof param === 'string') {
266
- conditions.push(param.toUpperCase());
267
- }
268
- });
269
- }
270
-
271
- const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(' ')}` : '';
278
+ const { sql: whereSql, bindings: values } = this._buildWhereClause(params);
279
+ const whereClause = whereSql ? ` WHERE ${whereSql}` : '';
272
280
 
273
281
  // Cláusulas adicionais
274
282
  const groupByClause = clauses['GROUPBY'] ? ` GROUP BY ${clauses['GROUPBY']}` : '';
@@ -292,14 +300,16 @@ class DatabaseHandler {
292
300
 
293
301
  async update(params, options = {}) {
294
302
  // Inicializa a string SQL para a cláusula SET
295
- const operator = options.useRegex ? 'REGEXP' : '=';
296
303
  let setClause = Object.keys(params.set).map(key => `\`${key}\` = ?`).join(', ');
304
+ let setValues = Object.values(params.set);
305
+
297
306
  // Inicializa a string SQL para a cláusula WHERE
298
- let whereClause = Object.keys(params.where).map(key => `\`${key}\` ${operator} ?`).join(' AND ');
307
+ const { sql: whereClause, bindings: whereValues } = this._buildWhereClause(params.where, !!options.useRegex);
308
+
299
309
  // Constrói a consulta SQL completa
300
310
  const sql = `UPDATE ${this.table} SET ${setClause} WHERE ${whereClause}`;
301
311
  // Combina os valores SET e WHERE em um único array
302
- const values = [...Object.values(params.set), ...Object.values(params.where)];
312
+ const values = [...setValues, ...whereValues];
303
313
  // Executa a consulta
304
314
  const result = await this.executeQuery(sql, values);
305
315
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@areumtecnologia/mysql-db-handler",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "A powerful MySQL wrapper with connection pooling, singleton support, and advanced query handling for Node.js.",
5
5
  "main": "index.js",
6
6
  "scripts": {