@bkmj/node-red-contrib-odbcmj 2.1.2 → 2.1.4
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 +0 -4
- package/odbc.html +2 -6
- package/odbc.js +159 -170
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,8 +49,6 @@ A **Test Connection** button in the configuration panel allows you to instantly
|
|
|
49
49
|
|
|
50
50
|
#### Pool Options
|
|
51
51
|
|
|
52
|
-
> **Note Importante :** Ces options de pool s'appliquent uniquement aux requêtes standards (**non-streamées**). Les requêtes en mode streaming gèrent leur propre connexion temporaire pour chaque exécution et n'utilisent **pas** le pool.
|
|
53
|
-
|
|
54
52
|
- **`initialSize`** `<number>` (optional): The number of connections to create when the pool is initialized. Default: 5.
|
|
55
53
|
- **`incrementSize`** `<number>` (optional): The number of connections to create when the pool is exhausted. Default: 5.
|
|
56
54
|
- **`maxSize`** `<number>` (optional): The maximum number of connections allowed in the pool. Default: 15.
|
|
@@ -60,8 +58,6 @@ A **Test Connection** button in the configuration panel allows you to instantly
|
|
|
60
58
|
|
|
61
59
|
#### Error Handling & Retry
|
|
62
60
|
|
|
63
|
-
> **Note Importante :** Cette logique de nouvelle tentative et de réinitialisation du pool s'applique aux requêtes standards (**non-streamées**). Une requête en mode streaming qui échoue remontera une erreur directement, sans déclencher ce mécanisme spécifique de nouvelle tentative.
|
|
64
|
-
|
|
65
61
|
- **`retryFreshConnection`** `<boolean>` (optional): If a query fails, the node will retry once with a brand new connection. If this succeeds, the entire connection pool is reset to clear any stale connections. Default: false.
|
|
66
62
|
- **`retryDelay`** `<number>` (optional): If both the pooled and the fresh connection attempts fail, this sets a delay in seconds before another retry is attempted. A value of **0** disables further automatic retries. Default: 5.
|
|
67
63
|
- **`retryOnMsg`** `<boolean>` (optional): If the node is waiting for a timed retry, a new incoming message can override the timer and trigger an immediate retry. Default: true.
|
package/odbc.html
CHANGED
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
<div class="form-row">
|
|
230
230
|
<label for="node-config-input-retryDelay"><i class="fa fa-history"></i> Retry Delay</label>
|
|
231
231
|
<input type="number" id="node-config-input-retryDelay" placeholder="5" style="width: 80px;" />
|
|
232
|
-
|
|
232
|
+
<span style="margin-left: 5px;">seconds</span>
|
|
233
233
|
</div>
|
|
234
234
|
<div class="form-row">
|
|
235
235
|
<label for="node-config-input-retryOnMsg" style="width: auto;"><i class="fa fa-envelope-o"></i> Retry on new message</label>
|
|
@@ -387,17 +387,13 @@ This mode gives you full control for complex or non-standard connection strings.
|
|
|
387
387
|
A **Test Connection** button in the configuration panel allows you to instantly verify your settings without deploying the flow.
|
|
388
388
|
|
|
389
389
|
### Pool Options
|
|
390
|
-
> **Important Note:** These pool options only apply to standard (non-streaming) queries. Streaming queries manage their own temporary connection for each execution and do **not** use the connection pool.
|
|
391
|
-
|
|
392
390
|
- **`initialSize`**: The number of connections to create when the pool is initialized. Default: 5.
|
|
393
391
|
- **`maxSize`**: The maximum number of connections allowed in the pool. Default: 15.
|
|
394
392
|
- (See `odbc` package documentation for more details on pool options).
|
|
395
393
|
|
|
396
394
|
### Error Handling & Retry
|
|
397
|
-
> **Important Note:** This retry logic applies to standard (non-streaming) queries. A streaming query that fails will report an error directly, without triggering this specific retry mechanism.
|
|
398
|
-
|
|
399
395
|
- **`retryFreshConnection`**: If a query fails, the node will retry once with a brand new connection. If this succeeds, the entire connection pool is reset to clear any stale connections.
|
|
400
|
-
- **`retryDelay
|
|
396
|
+
- **`retryDelay`**: If both attempts fail, this sets a delay in seconds before another retry is attempted. A value of **0** disables further automatic retries.
|
|
401
397
|
- **`retryOnMsg`**: If the node is waiting for a timed retry, a new incoming message can override the timer and trigger an immediate retry.
|
|
402
398
|
|
|
403
399
|
### Advanced
|
package/odbc.js
CHANGED
|
@@ -122,233 +122,222 @@ module.exports = function (RED) {
|
|
|
122
122
|
// ... (Pas de changement dans cette section)
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
|
|
126
|
+
// --- ODBC Query Node ---
|
|
126
127
|
function odbc(config) {
|
|
127
128
|
RED.nodes.createNode(this, config);
|
|
128
129
|
this.config = config;
|
|
129
130
|
this.poolNode = RED.nodes.getNode(this.config.connection);
|
|
130
131
|
this.name = this.config.name;
|
|
131
|
-
|
|
132
|
-
this.retryTimer = null;
|
|
132
|
+
// La logique de retry complexe est temporairement retirée pour stabiliser le noeud.
|
|
133
133
|
|
|
134
|
+
// Cette fonction reste inchangée
|
|
134
135
|
this.enhanceError = (error, query, params, defaultMessage = "Query error") => {
|
|
135
|
-
|
|
136
|
+
const queryContext = (() => {
|
|
137
|
+
let s = "";
|
|
138
|
+
if (query || params) {
|
|
139
|
+
s += " {";
|
|
140
|
+
if (query) s += `"query": '${query.substring(0, 100)}${query.length > 100 ? "..." : ""}'`;
|
|
141
|
+
if (params) s += `, "params": '${JSON.stringify(params)}'`;
|
|
142
|
+
s += "}";
|
|
143
|
+
return s;
|
|
144
|
+
}
|
|
145
|
+
return "";
|
|
146
|
+
})();
|
|
147
|
+
let finalError;
|
|
148
|
+
if (typeof error === "object" && error !== null && error.message) { finalError = error; }
|
|
149
|
+
else if (typeof error === "string") { finalError = new Error(error); }
|
|
150
|
+
else { finalError = new Error(defaultMessage); }
|
|
151
|
+
finalError.message = `${finalError.message}${queryContext}`;
|
|
152
|
+
if (query) finalError.query = query;
|
|
153
|
+
if (params) finalError.params = params;
|
|
154
|
+
return finalError;
|
|
136
155
|
};
|
|
156
|
+
|
|
157
|
+
// Cette fonction reste presque inchangée, elle est maintenant appelée depuis on("input")
|
|
158
|
+
this.executeQueryAndProcess = async (dbConnection, queryString, queryParams, msg) => {
|
|
159
|
+
const result = await dbConnection.query(queryString, queryParams);
|
|
137
160
|
|
|
138
|
-
|
|
139
|
-
|
|
161
|
+
if (typeof result === "undefined") { throw new Error("Query returned undefined."); }
|
|
162
|
+
const newMsg = RED.util.cloneMessage(msg);
|
|
163
|
+
const otherParams = {};
|
|
164
|
+
let actualDataRows = [];
|
|
165
|
+
if (result !== null && typeof result === "object") {
|
|
166
|
+
if (Array.isArray(result)) {
|
|
167
|
+
actualDataRows = [...result];
|
|
168
|
+
for (const [key, value] of Object.entries(result)) {
|
|
169
|
+
if (isNaN(parseInt(key))) { otherParams[key] = value; }
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
for (const [key, value] of Object.entries(result)) { otherParams[key] = value; }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const columnMetadata = otherParams.columns;
|
|
176
|
+
if (Array.isArray(columnMetadata) && Array.isArray(actualDataRows) && actualDataRows.length > 0) {
|
|
177
|
+
const sqlBitColumnNames = new Set();
|
|
178
|
+
columnMetadata.forEach((col) => {
|
|
179
|
+
if (col && typeof col.name === "string" && col.dataTypeName === "SQL_BIT") {
|
|
180
|
+
sqlBitColumnNames.add(col.name);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
if (sqlBitColumnNames.size > 0) {
|
|
184
|
+
actualDataRows.forEach((row) => {
|
|
185
|
+
if (typeof row === "object" && row !== null) {
|
|
186
|
+
for (const columnName of sqlBitColumnNames) {
|
|
187
|
+
if (row.hasOwnProperty(columnName)) {
|
|
188
|
+
const value = row[columnName];
|
|
189
|
+
if (value === "1" || value === 1) { row[columnName] = true; }
|
|
190
|
+
else if (value === "0" || value === 0) { row[columnName] = false; }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
objPath.set(newMsg, this.config.outputObj, actualDataRows);
|
|
198
|
+
if (Object.keys(otherParams).length) { newMsg.odbc = otherParams; }
|
|
199
|
+
return newMsg;
|
|
140
200
|
};
|
|
141
|
-
|
|
201
|
+
|
|
142
202
|
// =================================================================
|
|
143
|
-
//
|
|
203
|
+
// NOUVELLE IMPLEMENTATION DU STREAMING
|
|
144
204
|
// =================================================================
|
|
205
|
+
this.executeStreamQuery = async (dbConnection, queryString, queryParams, msg, send) => {
|
|
206
|
+
const chunkSize = parseInt(this.config.streamChunkSize) || 1;
|
|
207
|
+
// La taille du fetch peut être optimisée, mais restons simple pour la clarté.
|
|
208
|
+
const fetchSize = chunkSize > 50 ? 50 : chunkSize;
|
|
209
|
+
let cursor;
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
cursor = await dbConnection.query(queryString, queryParams, { cursor: true, fetchSize: fetchSize });
|
|
213
|
+
this.status({ fill: "blue", shape: "dot", text: "streaming rows..." });
|
|
145
214
|
|
|
146
|
-
this.executeStreamQuery = async (queryString, queryParams, msg, send, done) => {
|
|
147
|
-
const chunkSize = parseInt(this.config.streamChunkSize) || 1;
|
|
148
|
-
let cursor;
|
|
149
215
|
let rowCount = 0;
|
|
150
216
|
let chunk = [];
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
217
|
+
|
|
218
|
+
// Boucle infinie qui sera rompue de l'intérieur
|
|
219
|
+
while (true) {
|
|
220
|
+
const rows = await cursor.fetch();
|
|
221
|
+
|
|
222
|
+
// 1. VÉRIFIER D'ABORD LA FIN DU FLUX
|
|
223
|
+
if (!rows || rows.length === 0) {
|
|
224
|
+
// Le flux de la base de données est terminé.
|
|
225
|
+
// Le contenu actuel de `chunk` est le tout dernier lot.
|
|
226
|
+
if (chunk.length > 0) {
|
|
227
|
+
const newMsg = RED.util.cloneMessage(msg);
|
|
228
|
+
objPath.set(newMsg, this.config.outputObj, chunk);
|
|
229
|
+
// C'est le message final, donc `complete` est TRUE.
|
|
230
|
+
newMsg.odbc_stream = { index: rowCount - chunk.length, count: chunk.length, complete: true };
|
|
231
|
+
send(newMsg);
|
|
232
|
+
} else if (rowCount === 0) {
|
|
233
|
+
// Gérer le cas où la requête ne retourne aucune ligne.
|
|
234
|
+
const newMsg = RED.util.cloneMessage(msg);
|
|
235
|
+
objPath.set(newMsg, this.config.outputObj, []);
|
|
236
|
+
newMsg.odbc_stream = { index: 0, count: 0, complete: true };
|
|
237
|
+
send(newMsg);
|
|
238
|
+
}
|
|
239
|
+
// Quitter la boucle car il n'y a plus rien à faire.
|
|
240
|
+
break;
|
|
161
241
|
}
|
|
162
|
-
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
this.status({ fill: "blue", shape: "dot", text: "streaming rows..." });
|
|
167
|
-
let row = await cursor.fetch();
|
|
168
|
-
while (row) {
|
|
242
|
+
|
|
243
|
+
// 2. S'IL Y A DES LIGNES, LES TRAITER
|
|
244
|
+
for (const row of rows) {
|
|
169
245
|
rowCount++;
|
|
170
246
|
chunk.push(row);
|
|
171
247
|
if (chunk.length >= chunkSize) {
|
|
172
248
|
const newMsg = RED.util.cloneMessage(msg);
|
|
173
249
|
objPath.set(newMsg, this.config.outputObj, chunk);
|
|
250
|
+
// Ce lot n'est pas le dernier, donc `complete` est FALSE.
|
|
174
251
|
newMsg.odbc_stream = { index: rowCount - chunk.length, count: chunk.length, complete: false };
|
|
175
252
|
send(newMsg);
|
|
253
|
+
// Vider le lot pour le prochain remplissage.
|
|
176
254
|
chunk = [];
|
|
177
255
|
}
|
|
178
|
-
row = await cursor.fetch();
|
|
179
|
-
}
|
|
180
|
-
if (chunk.length > 0) {
|
|
181
|
-
const newMsg = RED.util.cloneMessage(msg);
|
|
182
|
-
objPath.set(newMsg, this.config.outputObj, chunk);
|
|
183
|
-
newMsg.odbc_stream = { index: rowCount - chunk.length, count: chunk.length, complete: true };
|
|
184
|
-
send(newMsg);
|
|
185
|
-
}
|
|
186
|
-
if (rowCount === 0) {
|
|
187
|
-
const newMsg = RED.util.cloneMessage(msg);
|
|
188
|
-
objPath.set(newMsg, this.config.outputObj, []);
|
|
189
|
-
newMsg.odbc_stream = { index: 0, count: 0, complete: true };
|
|
190
|
-
send(newMsg);
|
|
191
256
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
257
|
+
} // Fin de la boucle while
|
|
258
|
+
|
|
259
|
+
this.status({ fill: "green", shape: "dot", text: `success (${rowCount} rows)` });
|
|
260
|
+
|
|
261
|
+
} finally {
|
|
262
|
+
if (cursor) {
|
|
263
|
+
await cursor.close();
|
|
199
264
|
}
|
|
200
|
-
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
201
267
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
268
|
+
// =================================================================
|
|
269
|
+
// NOUVELLE LOGIQUE D'ENTREE UNIFIEE
|
|
270
|
+
// =================================================================
|
|
271
|
+
this.on("input", async (msg, send, done) => {
|
|
272
|
+
if (!this.poolNode) {
|
|
273
|
+
const err = new Error("ODBC Config node not properly configured.");
|
|
274
|
+
this.status({ fill: "red", shape: "ring", text: "No config node" });
|
|
275
|
+
done(err);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
208
278
|
|
|
279
|
+
let connection;
|
|
209
280
|
try {
|
|
210
281
|
this.status({ fill: "blue", shape: "dot", text: "preparing..." });
|
|
211
|
-
this.config.outputObj =
|
|
282
|
+
this.config.outputObj = this.config.outputObj || "payload";
|
|
212
283
|
|
|
284
|
+
// Obtenir la requête et les paramètres
|
|
213
285
|
const querySourceType = this.config.querySourceType || 'msg';
|
|
214
286
|
const querySource = this.config.querySource || 'query';
|
|
215
287
|
const paramsSourceType = this.config.paramsSourceType || 'msg';
|
|
216
288
|
const paramsSource = this.config.paramsSource || 'parameters';
|
|
217
289
|
|
|
218
|
-
|
|
219
|
-
RED.util.evaluateNodeProperty(paramsSource, paramsSourceType, this, msg, (err, value) =>
|
|
220
|
-
resolve(err ? undefined : value);
|
|
221
|
-
});
|
|
290
|
+
const currentQueryParams = await new Promise((resolve) => {
|
|
291
|
+
RED.util.evaluateNodeProperty(paramsSource, paramsSourceType, this, msg, (err, value) => resolve(err ? undefined : value));
|
|
222
292
|
});
|
|
223
293
|
|
|
224
294
|
let currentQueryString = await new Promise((resolve) => {
|
|
225
|
-
RED.util.evaluateNodeProperty(querySource, querySourceType, this, msg, (err, value) =>
|
|
226
|
-
resolve(err ? undefined : (value || this.config.query || ""));
|
|
227
|
-
});
|
|
295
|
+
RED.util.evaluateNodeProperty(querySource, querySourceType, this, msg, (err, value) => resolve(err ? undefined : (value || this.config.query || "")));
|
|
228
296
|
});
|
|
229
297
|
|
|
230
298
|
if (!currentQueryString) { throw new Error("No query to execute"); }
|
|
231
299
|
|
|
232
|
-
isPreparedStatement = currentQueryParams || (currentQueryString && currentQueryString.includes("?"));
|
|
300
|
+
const isPreparedStatement = currentQueryParams || (currentQueryString && currentQueryString.includes("?"));
|
|
233
301
|
if (!isPreparedStatement && currentQueryString) {
|
|
234
|
-
for (const parsed of mustache.parse(currentQueryString)) {
|
|
235
|
-
if ((parsed[0] === "name" || parsed[0] === "&") && !objPath.has(msg, parsed[1])) {
|
|
236
|
-
this.warn(`Mustache parameter "${parsed[1]}" is absent.`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
302
|
currentQueryString = mustache.render(currentQueryString, msg);
|
|
240
303
|
}
|
|
304
|
+
|
|
305
|
+
// Obtenir une connexion du pool
|
|
306
|
+
this.status({ fill: "yellow", shape: "dot", text: "connecting..." });
|
|
307
|
+
connection = await this.poolNode.connect();
|
|
308
|
+
this.status({ fill: "blue", shape: "dot", text: "executing..." });
|
|
241
309
|
|
|
242
310
|
if (this.config.streaming) {
|
|
243
|
-
await this.executeStreamQuery(currentQueryString, currentQueryParams, msg, send
|
|
311
|
+
await this.executeStreamQuery(connection, currentQueryString, currentQueryParams, msg, send);
|
|
244
312
|
} else {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
send(processedMsg);
|
|
249
|
-
if(done) done();
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
let firstAttemptError = null;
|
|
253
|
-
try {
|
|
254
|
-
connectionFromPool = await this.poolNode.connect();
|
|
255
|
-
await executeNonQuery(connectionFromPool);
|
|
256
|
-
return;
|
|
257
|
-
} catch (err) {
|
|
258
|
-
firstAttemptError = this.enhanceError(err, currentQueryString, currentQueryParams, "Query failed with pooled connection");
|
|
259
|
-
this.warn(`First attempt failed: ${firstAttemptError.message}`);
|
|
260
|
-
} finally {
|
|
261
|
-
if (connectionFromPool) await connectionFromPool.close();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (firstAttemptError) {
|
|
265
|
-
if (this.poolNode && this.poolNode.config.retryFreshConnection) {
|
|
266
|
-
this.log("Attempting retry with a fresh connection.");
|
|
267
|
-
this.status({ fill: "yellow", shape: "dot", text: "Retrying (fresh)..." });
|
|
268
|
-
let freshConnection = null;
|
|
269
|
-
try {
|
|
270
|
-
const freshConnectConfig = this.poolNode.getFreshConnectionConfig();
|
|
271
|
-
freshConnection = await odbcModule.connect(freshConnectConfig);
|
|
272
|
-
this.log("Fresh connection established for retry.");
|
|
273
|
-
await executeNonQuery(freshConnection);
|
|
274
|
-
this.log("Query successful with fresh connection. Resetting pool.");
|
|
275
|
-
await this.poolNode.resetPool();
|
|
276
|
-
return;
|
|
277
|
-
} catch (freshError) {
|
|
278
|
-
this.warn(`Retry with fresh connection also failed: ${freshError.message}`);
|
|
279
|
-
const retryDelay = parseInt(this.poolNode.config.retryDelay) || 0;
|
|
280
|
-
if (retryDelay > 0) {
|
|
281
|
-
this.isAwaitingRetry = true;
|
|
282
|
-
this.status({ fill: "red", shape: "ring", text: `Retry in ${retryDelay}s...` });
|
|
283
|
-
this.log(`Scheduling retry in ${retryDelay} seconds.`);
|
|
284
|
-
this.retryTimer = setTimeout(() => {
|
|
285
|
-
this.isAwaitingRetry = false;
|
|
286
|
-
this.log("Timer expired. Triggering scheduled retry.");
|
|
287
|
-
this.receive(msg);
|
|
288
|
-
}, retryDelay * 1000);
|
|
289
|
-
if (done) done();
|
|
290
|
-
} else {
|
|
291
|
-
throw this.enhanceError(freshError, currentQueryString, currentQueryParams, "Query failed on fresh connection retry");
|
|
292
|
-
}
|
|
293
|
-
} finally {
|
|
294
|
-
if (freshConnection) await freshConnection.close();
|
|
295
|
-
}
|
|
296
|
-
} else {
|
|
297
|
-
throw firstAttemptError;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
313
|
+
const newMsg = await this.executeQueryAndProcess(connection, currentQueryString, currentQueryParams, msg);
|
|
314
|
+
this.status({ fill: "green", shape: "dot", text: "success" });
|
|
315
|
+
send(newMsg);
|
|
300
316
|
}
|
|
317
|
+
|
|
318
|
+
// Si tout s'est bien passé, on appelle done() sans erreur
|
|
319
|
+
done();
|
|
320
|
+
|
|
301
321
|
} catch (err) {
|
|
302
|
-
const finalError = err
|
|
322
|
+
const finalError = this.enhanceError(err, null, null, "Query Execution Failed");
|
|
303
323
|
this.status({ fill: "red", shape: "ring", text: "query error" });
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
};
|
|
324
|
+
done(finalError); // On passe l'erreur à done() pour que Node-RED la gère
|
|
307
325
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// Pour le mode streaming, on n'a pas besoin d'attendre l'initialisation du *pool*,
|
|
317
|
-
// mais on a besoin du noeud de config.
|
|
318
|
-
if (this.config.streaming) {
|
|
319
|
-
await this.runQuery(msg, send, done);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// La logique ci-dessous ne s'applique qu'au mode non-streaming
|
|
324
|
-
if (this.poolNode.connecting) {
|
|
325
|
-
this.warn("Waiting for connection pool to initialize...");
|
|
326
|
-
this.status({ fill: "yellow", shape: "ring", text: "Waiting for pool" });
|
|
327
|
-
setTimeout(() => {
|
|
328
|
-
this.checkPool(msg, send, done).catch((err) => {
|
|
329
|
-
this.status({ fill: "red", shape: "dot", text: "Pool wait failed" });
|
|
330
|
-
if (done) { done(err); } else { this.error(err, msg); }
|
|
331
|
-
});
|
|
332
|
-
}, 1000);
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
if (!this.poolNode.pool) {
|
|
336
|
-
await this.poolNode.connect().then(c => c.close());
|
|
326
|
+
} finally {
|
|
327
|
+
if (connection) {
|
|
328
|
+
try {
|
|
329
|
+
await connection.close();
|
|
330
|
+
} catch (closeErr) {
|
|
331
|
+
this.warn(`Failed to close DB connection: ${closeErr.message}`);
|
|
332
|
+
}
|
|
337
333
|
}
|
|
338
|
-
await this.runQuery(msg, send, done);
|
|
339
|
-
} catch (err) {
|
|
340
|
-
const finalError = err instanceof Error ? err : new Error(String(err));
|
|
341
|
-
this.status({ fill: "red", shape: "dot", text: "Op failed" });
|
|
342
|
-
if (done) { done(finalError); } else { this.error(finalError, msg); }
|
|
343
334
|
}
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
this.on("input", async (msg, send, done) => {
|
|
347
|
-
// ... (Pas de changement dans cette section)
|
|
348
335
|
});
|
|
349
|
-
|
|
350
|
-
this.on("close",
|
|
351
|
-
|
|
336
|
+
|
|
337
|
+
this.on("close", (done) => {
|
|
338
|
+
this.status({});
|
|
339
|
+
// La logique de fermeture du pool est déjà dans le noeud de config
|
|
340
|
+
done();
|
|
352
341
|
});
|
|
353
342
|
|
|
354
343
|
if (this.poolNode) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bkmj/node-red-contrib-odbcmj",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"description": "A powerful Node-RED node to connect to any ODBC data source, with connection pooling, advanced retry logic, and result streaming.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|