@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.
Files changed (2) hide show
  1. package/odbc.js +89 -29
  2. 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.status({ fill: "red", shape: "ring", text: "retry failed" });
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.status({ fill: "red", shape: "ring", text: "query error" });
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.1",
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",