@areumtecnologia/mysql-db-handler 1.0.8 → 1.0.10
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 +49 -1
- package/index.js +3 -1
- package/lib/database-cache.js +2 -2
- package/lib/database.js +8 -4
- package/lib/databaseHandler.js +124 -65
- package/package.json +3 -3
- package/lib/database-singleton.js +0 -19
package/README.md
CHANGED
|
@@ -224,7 +224,9 @@ console.log('Inserted ID:', result.insertId);
|
|
|
224
224
|
|
|
225
225
|
### Updating Records (`update`)
|
|
226
226
|
|
|
227
|
-
Performs selective updates by specifying both a `set` payload and a `where` filter.
|
|
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)
|
|
228
230
|
|
|
229
231
|
```javascript
|
|
230
232
|
const result = await usersHandler.update({
|
|
@@ -239,6 +241,52 @@ const result = await usersHandler.update({
|
|
|
239
241
|
});
|
|
240
242
|
|
|
241
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` = ?
|
|
242
290
|
```
|
|
243
291
|
|
|
244
292
|
### Deleting Records (`delete`)
|
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Áreum Tecnologia - Renan Moreira - renan.moreira@areum.com.br
|
|
2
|
+
const DataBaseCache = require('./lib/database-cache');
|
|
2
3
|
const DataBase = require('./lib/database');
|
|
3
4
|
const DataBaseHandler = require('./lib/databaseHandler');
|
|
4
5
|
// We do not export the singleton by default in a library usually,
|
|
@@ -6,5 +7,6 @@ const DataBaseHandler = require('./lib/databaseHandler');
|
|
|
6
7
|
|
|
7
8
|
module.exports = {
|
|
8
9
|
DataBase,
|
|
9
|
-
DataBaseHandler
|
|
10
|
+
DataBaseHandler,
|
|
11
|
+
DataBaseCache
|
|
10
12
|
};
|
package/lib/database-cache.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const mysql = require('mysql2/promise');
|
|
3
3
|
const NodeCache = require('node-cache');
|
|
4
4
|
|
|
5
|
-
class
|
|
5
|
+
class DataBaseCache {
|
|
6
6
|
constructor(config = {}) {
|
|
7
7
|
this.config = config;
|
|
8
8
|
this.pool = mysql.createPool(this.config); // The pool is created once when the instance is created.
|
|
@@ -74,4 +74,4 @@ class DataBase {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
module.exports =
|
|
77
|
+
module.exports = DataBaseCache;
|
package/lib/database.js
CHANGED
|
@@ -31,8 +31,9 @@ class DataBase {
|
|
|
31
31
|
* Executes a SQL query using a pooled connection.
|
|
32
32
|
* @param {string} sql - The SQL query to execute.
|
|
33
33
|
* @param {Array} [params] - The parameters for the SQL query.
|
|
34
|
-
* @returns {Promise<Array>} The result set from the query
|
|
35
|
-
*
|
|
34
|
+
* @returns {Promise<Array|{error: Error}>} The result set from the query,
|
|
35
|
+
* or an object with an `error` property if the query fails.
|
|
36
|
+
* This prevents unhandled exceptions from crashing the application.
|
|
36
37
|
*/
|
|
37
38
|
async query(sql, params) {
|
|
38
39
|
let connection; // Mova a declaração da conexão para fora do bloco try
|
|
@@ -44,9 +45,12 @@ class DataBase {
|
|
|
44
45
|
const [rows] = await connection.query(sql, params);
|
|
45
46
|
return rows;
|
|
46
47
|
} catch (error) {
|
|
47
|
-
// Passo 3:
|
|
48
|
+
// Passo 3: Logar o erro para diagnóstico
|
|
48
49
|
console.error('Erro na execução da query:', error);
|
|
49
|
-
|
|
50
|
+
|
|
51
|
+
// Retorna o erro encapsulado em { error } para evitar crashes não tratados,
|
|
52
|
+
// mantendo o contrato documentado no README.
|
|
53
|
+
return { error };
|
|
50
54
|
} finally {
|
|
51
55
|
// Passo 4: Este bloco SEMPRE será executado, independentemente de sucesso ou falha no 'try'
|
|
52
56
|
if (connection) {
|
package/lib/databaseHandler.js
CHANGED
|
@@ -49,19 +49,101 @@ class DatabaseHandler {
|
|
|
49
49
|
const bindings = [];
|
|
50
50
|
for (const item of strictCondition) {
|
|
51
51
|
if (typeof item === 'string') {
|
|
52
|
+
// Operadores lógicos (AND / OR) são inseridos como-is, sem parênteses.
|
|
52
53
|
clauses.push(item.toUpperCase());
|
|
53
54
|
} else if (typeof item === 'object' && item !== null) {
|
|
54
55
|
const key = Object.keys(item).find(k => k !== 'operator');
|
|
55
56
|
if (!key) continue;
|
|
56
57
|
const value = item[key];
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
let operator = item.operator || '=';
|
|
59
|
+
let upperOp = String(operator).toUpperCase();
|
|
60
|
+
|
|
61
|
+
// Suporte a arrays => IN / NOT IN (consistente com _buildWhereClause)
|
|
62
|
+
if (Array.isArray(value) && upperOp === '=') {
|
|
63
|
+
operator = 'IN';
|
|
64
|
+
upperOp = 'IN';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (Array.isArray(value) && (upperOp === 'IN' || upperOp === 'NOT IN')) {
|
|
68
|
+
if (value.length === 0) {
|
|
69
|
+
clauses.push(upperOp === 'IN' ? '1 = 0' : '1 = 1');
|
|
70
|
+
} else {
|
|
71
|
+
const placeholders = value.map(() => '?').join(', ');
|
|
72
|
+
clauses.push(`\`${key}\` ${operator} (${placeholders})`);
|
|
73
|
+
bindings.push(...value);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
clauses.push(`\`${key}\` ${operator} ?`);
|
|
77
|
+
bindings.push(value);
|
|
78
|
+
}
|
|
60
79
|
}
|
|
61
80
|
}
|
|
81
|
+
// Junta com espaço para preservar operadores AND/OR intercalados.
|
|
82
|
+
// Ex: "(a = ? AND b = ?)" em vez de "(a = ? b = ?)" que era inválido.
|
|
62
83
|
return { sql: `(${clauses.join(' ')})`, bindings };
|
|
63
84
|
}
|
|
64
85
|
|
|
86
|
+
/**
|
|
87
|
+
* @private
|
|
88
|
+
* Constrói a cláusula WHERE (sem a palavra-chave WHERE) e seus bindings associados.
|
|
89
|
+
* @param {Object|Array} where - Condições do where.
|
|
90
|
+
* @param {boolean} useRegex - Se deve usar REGEXP por padrão (para compatibilidade com update).
|
|
91
|
+
* @returns {{ sql: string, bindings: Array }}
|
|
92
|
+
*/
|
|
93
|
+
_buildWhereClause(where, useRegex = false) {
|
|
94
|
+
if (!where) {
|
|
95
|
+
return { sql: '', bindings: [] };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const conditions = [];
|
|
99
|
+
const bindings = [];
|
|
100
|
+
|
|
101
|
+
if (Array.isArray(where)) {
|
|
102
|
+
where.forEach((param) => {
|
|
103
|
+
if (typeof param === 'object' && param !== null && !Array.isArray(param)) {
|
|
104
|
+
const entries = Object.entries(param);
|
|
105
|
+
if (entries.length === 0) return;
|
|
106
|
+
|
|
107
|
+
const [key, value] = entries[0];
|
|
108
|
+
const [operatorKey, operatorValue] = entries[1] || [];
|
|
109
|
+
|
|
110
|
+
let op = operatorValue ?? (useRegex ? 'REGEXP' : '=');
|
|
111
|
+
let upperOp = String(op).toUpperCase();
|
|
112
|
+
|
|
113
|
+
if (Array.isArray(value) && upperOp === '=') {
|
|
114
|
+
op = 'IN';
|
|
115
|
+
upperOp = 'IN';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (Array.isArray(value) && (upperOp === 'IN' || upperOp === 'NOT IN')) {
|
|
119
|
+
if (value.length === 0) {
|
|
120
|
+
conditions.push(upperOp === 'IN' ? '1 = 0' : '1 = 1');
|
|
121
|
+
} else {
|
|
122
|
+
const placeholders = value.map(() => '?').join(', ');
|
|
123
|
+
conditions.push(`\`${key}\` ${op} (${placeholders})`);
|
|
124
|
+
bindings.push(...value);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
const condition = `\`${key}\` ${op} ?`;
|
|
128
|
+
conditions.push(condition);
|
|
129
|
+
bindings.push(value);
|
|
130
|
+
}
|
|
131
|
+
} else if (typeof param === 'string') {
|
|
132
|
+
conditions.push(param.toUpperCase());
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return { sql: conditions.join(' '), bindings };
|
|
136
|
+
} else if (typeof where === 'object' && where !== null) {
|
|
137
|
+
const operator = useRegex ? 'REGEXP' : '=';
|
|
138
|
+
const keys = Object.keys(where);
|
|
139
|
+
const sql = keys.map(key => `\`${key}\` ${operator} ?`).join(' AND ');
|
|
140
|
+
const bindings = Object.values(where);
|
|
141
|
+
return { sql, bindings };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { sql: '', bindings: [] };
|
|
145
|
+
}
|
|
146
|
+
|
|
65
147
|
async selectToDatatable(rawDtQuery, strictCondition = []) {
|
|
66
148
|
// NOVO: Hidrata o objeto da requisição para o formato aninhado correto.
|
|
67
149
|
const dtQuery = this._hydrateObject(rawDtQuery);
|
|
@@ -121,12 +203,31 @@ class DatabaseHandler {
|
|
|
121
203
|
// --- Queries ---
|
|
122
204
|
const baseWhereSql = baseWhere.sql ? `WHERE ${baseWhere.sql}` : '';
|
|
123
205
|
const totalCountSql = `SELECT COUNT(*) as total FROM ${this.table} ${baseWhereSql}`;
|
|
124
|
-
const
|
|
125
|
-
|
|
206
|
+
const totalResult = await this.executeQuery(totalCountSql, baseWhere.bindings);
|
|
207
|
+
// Contrato: executeQuery propaga { error } em caso de falha (não lança).
|
|
208
|
+
if (totalResult && totalResult.error) {
|
|
209
|
+
return {
|
|
210
|
+
draw: parseInt(draw || 0),
|
|
211
|
+
recordsTotal: 0,
|
|
212
|
+
recordsFiltered: 0,
|
|
213
|
+
data: [],
|
|
214
|
+
error: totalResult.error.message
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const recordsTotal = (totalResult && totalResult[0] && totalResult[0].total) || 0;
|
|
126
218
|
|
|
127
219
|
const filteredCountSql = `SELECT COUNT(*) as total FROM ${this.table} ${whereSql}`;
|
|
128
|
-
const
|
|
129
|
-
|
|
220
|
+
const filteredResult = await this.executeQuery(filteredCountSql, finalBindings);
|
|
221
|
+
if (filteredResult && filteredResult.error) {
|
|
222
|
+
return {
|
|
223
|
+
draw: parseInt(draw || 0),
|
|
224
|
+
recordsTotal: parseInt(recordsTotal),
|
|
225
|
+
recordsFiltered: 0,
|
|
226
|
+
data: [],
|
|
227
|
+
error: filteredResult.error.message
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const recordsFiltered = (filteredResult && filteredResult[0] && filteredResult[0].total) || 0;
|
|
130
231
|
|
|
131
232
|
// CORREÇÃO: Construção segura da cláusula SELECT para evitar vírgulas duplas.
|
|
132
233
|
const selectFields = ['*'];
|
|
@@ -136,6 +237,15 @@ class DatabaseHandler {
|
|
|
136
237
|
}
|
|
137
238
|
const dataSql = `SELECT ${selectFields.join(', ')} FROM ${this.table} ${whereSql} ${orderSql} ${limitSql}`;
|
|
138
239
|
const data = await this.executeQuery(dataSql, finalBindings);
|
|
240
|
+
if (data && data.error) {
|
|
241
|
+
return {
|
|
242
|
+
draw: parseInt(draw || 0),
|
|
243
|
+
recordsTotal: parseInt(recordsTotal),
|
|
244
|
+
recordsFiltered: parseInt(recordsFiltered),
|
|
245
|
+
data: [],
|
|
246
|
+
error: data.error.message
|
|
247
|
+
};
|
|
248
|
+
}
|
|
139
249
|
|
|
140
250
|
return {
|
|
141
251
|
draw: parseInt(draw || 0),
|
|
@@ -214,61 +324,8 @@ class DatabaseHandler {
|
|
|
214
324
|
return new Error('O parâmetro "params" deve ser um array');
|
|
215
325
|
}
|
|
216
326
|
|
|
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(' ')}` : '';
|
|
327
|
+
const { sql: whereSql, bindings: values } = this._buildWhereClause(params);
|
|
328
|
+
const whereClause = whereSql ? ` WHERE ${whereSql}` : '';
|
|
272
329
|
|
|
273
330
|
// Cláusulas adicionais
|
|
274
331
|
const groupByClause = clauses['GROUPBY'] ? ` GROUP BY ${clauses['GROUPBY']}` : '';
|
|
@@ -292,14 +349,16 @@ class DatabaseHandler {
|
|
|
292
349
|
|
|
293
350
|
async update(params, options = {}) {
|
|
294
351
|
// Inicializa a string SQL para a cláusula SET
|
|
295
|
-
const operator = options.useRegex ? 'REGEXP' : '=';
|
|
296
352
|
let setClause = Object.keys(params.set).map(key => `\`${key}\` = ?`).join(', ');
|
|
353
|
+
let setValues = Object.values(params.set);
|
|
354
|
+
|
|
297
355
|
// Inicializa a string SQL para a cláusula WHERE
|
|
298
|
-
|
|
356
|
+
const { sql: whereClause, bindings: whereValues } = this._buildWhereClause(params.where, !!options.useRegex);
|
|
357
|
+
|
|
299
358
|
// Constrói a consulta SQL completa
|
|
300
359
|
const sql = `UPDATE ${this.table} SET ${setClause} WHERE ${whereClause}`;
|
|
301
360
|
// Combina os valores SET e WHERE em um único array
|
|
302
|
-
const values = [...
|
|
361
|
+
const values = [...setValues, ...whereValues];
|
|
303
362
|
// Executa a consulta
|
|
304
363
|
const result = await this.executeQuery(sql, values);
|
|
305
364
|
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@areumtecnologia/mysql-db-handler",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
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": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "node tests/update_operators.test.js && node tests/selectToDatatable.test.js"
|
|
8
8
|
},
|
|
9
9
|
"author": "Renan Moreira / Áreum Tecnologia",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"mysql2": "^3.
|
|
12
|
+
"mysql2": "^3.23.1"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// Áreum Tecnologia - Renan Moreira - renan.moreira@areum.com.br
|
|
2
|
-
// src/database/database-singleton.js
|
|
3
|
-
// Exemplo Singleton para a conexão com o banco de dados
|
|
4
|
-
const { DB_HOST, DB_USER, DB_PASS } = require('../constants');
|
|
5
|
-
const DataBase = require("./database");
|
|
6
|
-
|
|
7
|
-
const dbi = new DataBase({
|
|
8
|
-
host: DB_HOST,
|
|
9
|
-
user: DB_USER,
|
|
10
|
-
password: DB_PASS,
|
|
11
|
-
database: '',
|
|
12
|
-
charset: "utf8mb4",
|
|
13
|
-
timezone: "-03:00",
|
|
14
|
-
connectionLimit: 1, // Ajustado para um limite de conexão mais comum
|
|
15
|
-
waitForConnections: true,
|
|
16
|
-
queueLimit: 0
|
|
17
|
-
});
|
|
18
|
-
// console.log("DATABASE SINGLETON CREATED", dbi.id);
|
|
19
|
-
module.exports = dbi;
|