@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 +314 -118
- package/lib/databaseHandler.js +68 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,48 @@
|
|
|
1
1
|
# @areumtecnologia/mysql-db-handler
|
|
2
2
|
|
|
3
|
-
A powerful MySQL wrapper
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
const
|
|
115
|
+
const db = new DataBase({ /* config */ });
|
|
116
|
+
const usersHandler = new DataBaseHandler(db, 'users');
|
|
57
117
|
```
|
|
58
118
|
|
|
59
119
|
---
|
|
60
120
|
|
|
61
|
-
## Advanced
|
|
62
|
-
|
|
63
|
-
### Inserting Records (`insert`)
|
|
121
|
+
## Advanced Querying (`select` method)
|
|
64
122
|
|
|
65
|
-
The `
|
|
123
|
+
The `select(params, clauses)` method builds complex SQL `WHERE` clauses from arrays of condition objects.
|
|
66
124
|
|
|
67
|
-
|
|
68
|
-
* `params` (Object): An object where keys match the column names and values are the data to insert.
|
|
125
|
+
### Basic Selection
|
|
69
126
|
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
152
|
+
#### Explicit `IN` / `NOT IN` Operator
|
|
153
|
+
You can explicitly define the `operator` as `'IN'` or `'NOT IN'`:
|
|
85
154
|
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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' },
|
|
190
|
+
{ category: 'electronics' },
|
|
103
191
|
'AND',
|
|
104
|
-
{ price:
|
|
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':
|
|
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
|
-
|
|
206
|
+
---
|
|
117
207
|
|
|
118
|
-
|
|
208
|
+
## CRUD Operations
|
|
119
209
|
|
|
120
|
-
|
|
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
|
-
|
|
212
|
+
Adds a new row to the associated table using key-value properties.
|
|
127
213
|
|
|
128
214
|
```javascript
|
|
129
|
-
const
|
|
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
|
-
|
|
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: '
|
|
134
|
-
|
|
234
|
+
status: 'suspended',
|
|
235
|
+
notes: 'Violated terms of service'
|
|
135
236
|
},
|
|
136
237
|
where: {
|
|
137
|
-
id:
|
|
138
|
-
|
|
238
|
+
id: 42,
|
|
239
|
+
status: 'active'
|
|
139
240
|
}
|
|
140
241
|
});
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
302
|
+
console.log('Deleted rows:', result.affectedRows);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
150
306
|
|
|
151
|
-
|
|
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
|
-
|
|
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
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
* **config**: Object. MySQL2 connection configuration (host, user, password, database, port, connectionLimit, etc).
|
|
350
|
+
module.exports = dbInstance;
|
|
351
|
+
```
|
|
172
352
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
368
|
+
## Error Handling Strategy
|
|
186
369
|
|
|
187
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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.
|
|
227
|
-
2.
|
|
228
|
-
3.
|
|
229
|
-
4.
|
|
230
|
-
5.
|
|
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
|
package/lib/databaseHandler.js
CHANGED
|
@@ -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
|
|
218
|
-
const
|
|
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
|
-
|
|
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 = [...
|
|
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