@bkmj/node-red-contrib-odbcmj 2.2.1 → 2.2.2
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/odbc.js +89 -29
- package/package.json +1 -1
package/odbc.js
CHANGED
|
@@ -181,6 +181,10 @@ module.exports = function (RED) {
|
|
|
181
181
|
this.poolNode = RED.nodes.getNode(this.config.connection);
|
|
182
182
|
this.name = this.config.name;
|
|
183
183
|
|
|
184
|
+
// Propriétés pour la logique de retry temporisée
|
|
185
|
+
this.isAwaitingRetry = false;
|
|
186
|
+
this.retryTimer = null;
|
|
187
|
+
|
|
184
188
|
this.enhanceError = (error, query, params, defaultMessage = "Query error") => {
|
|
185
189
|
const queryContext = (() => {
|
|
186
190
|
let s = "";
|
|
@@ -204,6 +208,7 @@ module.exports = function (RED) {
|
|
|
204
208
|
};
|
|
205
209
|
|
|
206
210
|
this.executeQueryAndProcess = async (dbConnection, queryString, queryParams, msg) => {
|
|
211
|
+
// ... (contenu de cette fonction inchangé par rapport à la dernière version)
|
|
207
212
|
const result = await dbConnection.query(queryString, queryParams);
|
|
208
213
|
if (typeof result === "undefined") { throw new Error("Query returned undefined."); }
|
|
209
214
|
const newMsg = RED.util.cloneMessage(msg);
|
|
@@ -247,23 +252,18 @@ module.exports = function (RED) {
|
|
|
247
252
|
};
|
|
248
253
|
|
|
249
254
|
this.executeStreamQuery = async (dbConnection, queryString, queryParams, msg, send) => {
|
|
255
|
+
// ... (contenu de cette fonction inchangé par rapport à la dernière version avec le message de complétion final)
|
|
250
256
|
const chunkSize = parseInt(this.config.streamChunkSize) || 1;
|
|
251
257
|
const fetchSize = chunkSize > 100 ? 100 : chunkSize;
|
|
252
258
|
let cursor;
|
|
253
|
-
|
|
254
259
|
try {
|
|
255
260
|
cursor = await dbConnection.query(queryString, queryParams, { cursor: true, fetchSize: fetchSize });
|
|
256
261
|
this.status({ fill: "blue", shape: "dot", text: "streaming rows..." });
|
|
257
|
-
|
|
258
262
|
let rowCount = 0;
|
|
259
263
|
let chunk = [];
|
|
260
|
-
|
|
261
264
|
while (true) {
|
|
262
265
|
const rows = await cursor.fetch();
|
|
263
|
-
if (!rows || rows.length === 0) {
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
|
|
266
|
+
if (!rows || rows.length === 0) { break; }
|
|
267
267
|
for (const row of rows) {
|
|
268
268
|
rowCount++;
|
|
269
269
|
chunk.push(row);
|
|
@@ -276,51 +276,63 @@ module.exports = function (RED) {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
-
|
|
280
279
|
if (chunk.length > 0) {
|
|
281
280
|
const newMsg = RED.util.cloneMessage(msg);
|
|
282
281
|
objPath.set(newMsg, this.config.outputObj, chunk);
|
|
283
282
|
newMsg.odbc_stream = { index: rowCount - chunk.length, count: chunk.length, complete: false };
|
|
284
283
|
send(newMsg);
|
|
285
284
|
}
|
|
286
|
-
|
|
287
285
|
const finalMsg = RED.util.cloneMessage(msg);
|
|
288
286
|
objPath.set(finalMsg, this.config.outputObj, []);
|
|
289
287
|
finalMsg.odbc_stream = { index: rowCount, count: 0, complete: true };
|
|
290
288
|
send(finalMsg);
|
|
291
|
-
|
|
292
289
|
this.status({ fill: "green", shape: "dot", text: `success (${rowCount} rows)` });
|
|
293
|
-
|
|
294
290
|
} finally {
|
|
295
|
-
if (cursor)
|
|
296
|
-
await cursor.close();
|
|
297
|
-
}
|
|
291
|
+
if (cursor) await cursor.close();
|
|
298
292
|
}
|
|
299
293
|
};
|
|
300
294
|
|
|
301
295
|
this.on("input", async (msg, send, done) => {
|
|
296
|
+
// --- NOUVEAU : GESTION DE retryOnMsg ---
|
|
297
|
+
if (this.isAwaitingRetry) {
|
|
298
|
+
if (this.poolNode && this.poolNode.config.retryOnMsg === true) { // s'assurer que c'est bien un booléen true
|
|
299
|
+
this.log("New message received, overriding retry timer and attempting query now.");
|
|
300
|
+
clearTimeout(this.retryTimer);
|
|
301
|
+
this.retryTimer = null;
|
|
302
|
+
this.isAwaitingRetry = false;
|
|
303
|
+
// Laisser l'exécution se poursuivre
|
|
304
|
+
} else {
|
|
305
|
+
this.warn("Node is in a retry-wait state. New message ignored as per configuration.");
|
|
306
|
+
if (done) done(); // Terminer le traitement pour CE message
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// S'assurer que les états de retry sont propres si on n'est pas dans un retry forcé par un nouveau message
|
|
311
|
+
this.isAwaitingRetry = false;
|
|
312
|
+
if(this.retryTimer) {
|
|
313
|
+
clearTimeout(this.retryTimer);
|
|
314
|
+
this.retryTimer = null;
|
|
315
|
+
}
|
|
316
|
+
// --- FIN DE LA GESTION DE retryOnMsg ---
|
|
317
|
+
|
|
302
318
|
if (!this.poolNode) {
|
|
319
|
+
this.status({ fill: "red", shape: "ring", text: "No config node" });
|
|
303
320
|
return done(new Error("ODBC Config node not properly configured."));
|
|
304
321
|
}
|
|
305
|
-
|
|
322
|
+
|
|
306
323
|
const execute = async (connection) => {
|
|
307
324
|
this.config.outputObj = this.config.outputObj || "payload";
|
|
308
|
-
|
|
309
325
|
const querySourceType = this.config.querySourceType || 'msg';
|
|
310
326
|
const querySource = this.config.querySource || 'query';
|
|
311
327
|
const paramsSourceType = this.config.paramsSourceType || 'msg';
|
|
312
328
|
const paramsSource = this.config.paramsSource || 'parameters';
|
|
313
|
-
|
|
314
329
|
const params = await new Promise(resolve => RED.util.evaluateNodeProperty(paramsSource, paramsSourceType, this, msg, (err, val) => resolve(err ? undefined : val)));
|
|
315
330
|
let query = await new Promise(resolve => RED.util.evaluateNodeProperty(querySource, querySourceType, this, msg, (err, val) => resolve(err ? undefined : (val || this.config.query || ""))));
|
|
316
|
-
|
|
317
331
|
if (!query) throw new Error("No query to execute");
|
|
318
|
-
|
|
319
332
|
const isPreparedStatement = params || (query && query.includes("?"));
|
|
320
333
|
if (!isPreparedStatement && query) {
|
|
321
334
|
query = mustache.render(query, msg);
|
|
322
335
|
}
|
|
323
|
-
|
|
324
336
|
this.status({ fill: "blue", shape: "dot", text: "executing..." });
|
|
325
337
|
if (this.config.streaming) {
|
|
326
338
|
await this.executeStreamQuery(connection, query, params, msg, send);
|
|
@@ -330,13 +342,15 @@ module.exports = function (RED) {
|
|
|
330
342
|
send(newMsg);
|
|
331
343
|
}
|
|
332
344
|
};
|
|
333
|
-
|
|
345
|
+
|
|
334
346
|
let connectionFromPool;
|
|
347
|
+
let errorAfterInitialAttempts = null;
|
|
348
|
+
|
|
335
349
|
try {
|
|
336
350
|
this.status({ fill: "yellow", shape: "dot", text: "connecting..." });
|
|
337
351
|
connectionFromPool = await this.poolNode.connect();
|
|
338
352
|
await execute(connectionFromPool);
|
|
339
|
-
return done();
|
|
353
|
+
return done(); // Succès de la première tentative
|
|
340
354
|
} catch (poolError) {
|
|
341
355
|
this.warn(`First attempt with pooled connection failed: ${poolError.message}`);
|
|
342
356
|
if (this.poolNode.config.retryFreshConnection) {
|
|
@@ -350,27 +364,73 @@ module.exports = function (RED) {
|
|
|
350
364
|
await execute(freshConnection);
|
|
351
365
|
this.log("Query successful with fresh connection. Resetting pool.");
|
|
352
366
|
await this.poolNode.resetPool();
|
|
353
|
-
return done();
|
|
367
|
+
return done(); // Succès de la tentative avec connexion fraîche
|
|
354
368
|
} catch (freshError) {
|
|
355
|
-
this.
|
|
356
|
-
return done(this.enhanceError(freshError, null, null, "Retry with fresh connection also failed"));
|
|
369
|
+
errorAfterInitialAttempts = this.enhanceError(freshError, null, null, "Retry with fresh connection also failed");
|
|
357
370
|
} finally {
|
|
358
371
|
if (freshConnection) await freshConnection.close();
|
|
359
372
|
}
|
|
360
373
|
} else {
|
|
361
|
-
this.
|
|
362
|
-
return done(this.enhanceError(poolError));
|
|
374
|
+
errorAfterInitialAttempts = this.enhanceError(poolError);
|
|
363
375
|
}
|
|
364
376
|
} finally {
|
|
365
377
|
if (connectionFromPool) await connectionFromPool.close();
|
|
366
378
|
}
|
|
379
|
+
|
|
380
|
+
// --- NOUVEAU : GESTION DE retryDelay ---
|
|
381
|
+
if (errorAfterInitialAttempts) {
|
|
382
|
+
const retryDelaySeconds = parseInt(this.poolNode.config.retryDelay, 10); // S'assurer que c'est un nombre
|
|
383
|
+
|
|
384
|
+
if (retryDelaySeconds > 0) {
|
|
385
|
+
this.warn(`Query failed. Scheduling retry in ${retryDelaySeconds} seconds. Error: ${errorAfterInitialAttempts.message}`);
|
|
386
|
+
this.status({ fill: "red", shape: "ring", text: `Retry in ${retryDelaySeconds}s...` });
|
|
387
|
+
this.isAwaitingRetry = true;
|
|
388
|
+
|
|
389
|
+
// Important: `this.receive(msg)` ne peut pas être appelé directement dans un `setTimeout`
|
|
390
|
+
// sans s'assurer que `this` est correctement lié. Utiliser une arrow function ou .bind(this).
|
|
391
|
+
// De plus, `this.receive` est une méthode non documentée pour réinjecter un message.
|
|
392
|
+
// La méthode standard pour retenter est que le nœud se renvoie le message à lui-même.
|
|
393
|
+
// Pour cela, le `done()` de l'invocation actuelle doit être appelé.
|
|
394
|
+
|
|
395
|
+
this.retryTimer = setTimeout(() => {
|
|
396
|
+
this.isAwaitingRetry = false; // Prêt pour une nouvelle tentative
|
|
397
|
+
this.retryTimer = null;
|
|
398
|
+
this.log(`Retry timer expired for message. Re-emitting for node ${this.id || this.name}.`);
|
|
399
|
+
// Réinjecter le message pour une nouvelle tentative de traitement.
|
|
400
|
+
// Le message original `msg` est utilisé.
|
|
401
|
+
this.receive(msg);
|
|
402
|
+
}, retryDelaySeconds * 1000);
|
|
403
|
+
|
|
404
|
+
// L'invocation actuelle du message se termine ici, sans erreur si un retry est planifié.
|
|
405
|
+
// L'erreur sera gérée par la prochaine invocation si elle échoue à nouveau.
|
|
406
|
+
if (done) return done();
|
|
407
|
+
|
|
408
|
+
} else {
|
|
409
|
+
// Pas de retryDelay configuré ou il est à 0. C'est une défaillance définitive pour CE message.
|
|
410
|
+
this.status({ fill: "red", shape: "ring", text: "query error" });
|
|
411
|
+
if (done) return done(errorAfterInitialAttempts);
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
// Normalement, on ne devrait pas arriver ici si done() a été appelé après un succès.
|
|
415
|
+
// C'est une sécurité.
|
|
416
|
+
if (done) return done();
|
|
417
|
+
}
|
|
418
|
+
// --- FIN DE LA GESTION DE retryDelay ---
|
|
367
419
|
});
|
|
368
420
|
|
|
369
421
|
this.on("close", (done) => {
|
|
422
|
+
// --- NOUVEAU : Nettoyage du timer ---
|
|
423
|
+
if (this.retryTimer) {
|
|
424
|
+
clearTimeout(this.retryTimer);
|
|
425
|
+
this.retryTimer = null;
|
|
426
|
+
this.isAwaitingRetry = false;
|
|
427
|
+
this.log("Cleared pending retry timer on node close/redeploy.");
|
|
428
|
+
}
|
|
429
|
+
// --- FIN DU NETTOYAGE ---
|
|
370
430
|
this.status({});
|
|
371
431
|
done();
|
|
372
432
|
});
|
|
373
|
-
|
|
433
|
+
|
|
374
434
|
if (this.poolNode) {
|
|
375
435
|
this.status({ fill: "green", shape: "dot", text: "ready" });
|
|
376
436
|
} else {
|
|
@@ -378,6 +438,6 @@ module.exports = function (RED) {
|
|
|
378
438
|
this.warn("ODBC Config node not found or not deployed.");
|
|
379
439
|
}
|
|
380
440
|
}
|
|
381
|
-
|
|
441
|
+
|
|
382
442
|
RED.nodes.registerType("odbc", odbc);
|
|
383
443
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bkmj/node-red-contrib-odbcmj",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
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",
|