@bkmj/node-red-contrib-odbcmj 2.1.0 → 2.1.1
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 +86 -66
- package/package.json +1 -1
package/odbc.js
CHANGED
|
@@ -257,14 +257,23 @@ module.exports = function (RED) {
|
|
|
257
257
|
return newMsg;
|
|
258
258
|
};
|
|
259
259
|
|
|
260
|
-
|
|
260
|
+
// =================================================================
|
|
261
|
+
// DEBUT DE LA SECTION CORRIGÉE
|
|
262
|
+
// =================================================================
|
|
263
|
+
|
|
264
|
+
this.executeStreamQuery = async (queryString, queryParams, msg, send, done) => {
|
|
261
265
|
const chunkSize = parseInt(this.config.streamChunkSize) || 1;
|
|
262
266
|
let cursor;
|
|
263
267
|
let rowCount = 0;
|
|
264
268
|
let chunk = [];
|
|
265
269
|
|
|
266
270
|
try {
|
|
267
|
-
|
|
271
|
+
// CORRECTION : Appeler .cursor() sur le pool, pas sur une connexion individuelle.
|
|
272
|
+
if (!this.poolNode || !this.poolNode.pool) {
|
|
273
|
+
throw new Error("Le pool de connexions n'est pas initialisé pour le streaming.");
|
|
274
|
+
}
|
|
275
|
+
cursor = await this.poolNode.pool.cursor(queryString, queryParams);
|
|
276
|
+
|
|
268
277
|
this.status({ fill: "blue", shape: "dot", text: "streaming rows..." });
|
|
269
278
|
let row = await cursor.fetch();
|
|
270
279
|
while (row) {
|
|
@@ -294,6 +303,7 @@ module.exports = function (RED) {
|
|
|
294
303
|
this.status({ fill: "green", shape: "dot", text: `success (${rowCount} rows)` });
|
|
295
304
|
if(done) done();
|
|
296
305
|
} catch(err) {
|
|
306
|
+
// L'erreur sera transmise à l'appelant (runQuery)
|
|
297
307
|
throw err;
|
|
298
308
|
}
|
|
299
309
|
finally {
|
|
@@ -302,10 +312,10 @@ module.exports = function (RED) {
|
|
|
302
312
|
};
|
|
303
313
|
|
|
304
314
|
this.runQuery = async (msg, send, done) => {
|
|
305
|
-
|
|
306
|
-
|
|
315
|
+
let isPreparedStatement = false;
|
|
316
|
+
let connectionFromPool = null;
|
|
307
317
|
|
|
308
|
-
|
|
318
|
+
try {
|
|
309
319
|
this.status({ fill: "blue", shape: "dot", text: "preparing..." });
|
|
310
320
|
this.config.outputObj = msg?.output || this.config?.outputObj || "payload";
|
|
311
321
|
|
|
@@ -314,18 +324,16 @@ module.exports = function (RED) {
|
|
|
314
324
|
const paramsSourceType = this.config.paramsSourceType || 'msg';
|
|
315
325
|
const paramsSource = this.config.paramsSource || 'parameters';
|
|
316
326
|
|
|
317
|
-
let currentQueryParams = await new Promise((resolve
|
|
327
|
+
let currentQueryParams = await new Promise((resolve) => {
|
|
318
328
|
RED.util.evaluateNodeProperty(paramsSource, paramsSourceType, this, msg, (err, value) => {
|
|
319
|
-
|
|
320
|
-
else { resolve(value); }
|
|
329
|
+
resolve(err ? undefined : value);
|
|
321
330
|
});
|
|
322
331
|
});
|
|
323
332
|
|
|
324
|
-
let currentQueryString = await new Promise((resolve
|
|
333
|
+
let currentQueryString = await new Promise((resolve) => {
|
|
325
334
|
RED.util.evaluateNodeProperty(querySource, querySourceType, this, msg, (err, value) => {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
});
|
|
335
|
+
resolve(err ? undefined : (value || this.config.query || ""));
|
|
336
|
+
});
|
|
329
337
|
});
|
|
330
338
|
|
|
331
339
|
if (!currentQueryString) { throw new Error("No query to execute"); }
|
|
@@ -340,71 +348,79 @@ module.exports = function (RED) {
|
|
|
340
348
|
currentQueryString = mustache.render(currentQueryString, msg);
|
|
341
349
|
}
|
|
342
350
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
351
|
+
// CORRECTION : Logique séparée pour streaming et non-streaming
|
|
352
|
+
if (this.config.streaming) {
|
|
353
|
+
// Le mode Streaming appelle directement la fonction corrigée
|
|
354
|
+
await this.executeStreamQuery(currentQueryString, currentQueryParams, msg, send, done);
|
|
355
|
+
|
|
356
|
+
} else {
|
|
357
|
+
// Le mode non-streaming utilise la logique de connexion/retry existante
|
|
358
|
+
const executeNonQuery = async (conn) => {
|
|
347
359
|
const processedMsg = await this.executeQueryAndProcess(conn, currentQueryString, currentQueryParams, isPreparedStatement, msg);
|
|
348
360
|
this.status({ fill: "green", shape: "dot", text: "success" });
|
|
349
361
|
send(processedMsg);
|
|
350
362
|
if(done) done();
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
let firstAttemptError = null;
|
|
366
|
+
try {
|
|
367
|
+
connectionFromPool = await this.poolNode.connect();
|
|
368
|
+
await executeNonQuery(connectionFromPool);
|
|
369
|
+
return;
|
|
370
|
+
} catch (err) {
|
|
371
|
+
firstAttemptError = this.enhanceError(err, currentQueryString, currentQueryParams, "Query failed with pooled connection");
|
|
372
|
+
this.warn(`First attempt failed: ${firstAttemptError.message}`);
|
|
373
|
+
} finally {
|
|
374
|
+
if (connectionFromPool) await connectionFromPool.close();
|
|
351
375
|
}
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
let firstAttemptError = null;
|
|
355
|
-
try {
|
|
356
|
-
connectionFromPool = await this.poolNode.connect();
|
|
357
|
-
await execute(connectionFromPool);
|
|
358
|
-
return;
|
|
359
|
-
} catch (err) {
|
|
360
|
-
firstAttemptError = this.enhanceError(err, currentQueryString, currentQueryParams, "Query failed with pooled connection");
|
|
361
|
-
this.warn(`First attempt failed: ${firstAttemptError.message}`);
|
|
362
|
-
} finally {
|
|
363
|
-
if (connectionFromPool) await connectionFromPool.close();
|
|
364
|
-
}
|
|
365
376
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
377
|
+
if (firstAttemptError) {
|
|
378
|
+
if (this.poolNode && this.poolNode.config.retryFreshConnection) {
|
|
379
|
+
this.log("Attempting retry with a fresh connection.");
|
|
380
|
+
this.status({ fill: "yellow", shape: "dot", text: "Retrying (fresh)..." });
|
|
381
|
+
let freshConnection = null;
|
|
382
|
+
try {
|
|
383
|
+
const freshConnectConfig = this.poolNode.getFreshConnectionConfig();
|
|
384
|
+
freshConnection = await odbcModule.connect(freshConnectConfig);
|
|
385
|
+
this.log("Fresh connection established for retry.");
|
|
386
|
+
await executeNonQuery(freshConnection);
|
|
387
|
+
this.log("Query successful with fresh connection. Resetting pool.");
|
|
388
|
+
await this.poolNode.resetPool();
|
|
389
|
+
return;
|
|
390
|
+
} catch (freshError) {
|
|
391
|
+
this.warn(`Retry with fresh connection also failed: ${freshError.message}`);
|
|
392
|
+
const retryDelay = parseInt(this.poolNode.config.retryDelay) || 0;
|
|
393
|
+
if (retryDelay > 0) {
|
|
394
|
+
this.isAwaitingRetry = true;
|
|
395
|
+
this.status({ fill: "red", shape: "ring", text: `Retry in ${retryDelay}s...` });
|
|
396
|
+
this.log(`Scheduling retry in ${retryDelay} seconds.`);
|
|
397
|
+
this.retryTimer = setTimeout(() => {
|
|
398
|
+
this.isAwaitingRetry = false;
|
|
399
|
+
this.log("Timer expired. Triggering scheduled retry.");
|
|
400
|
+
this.receive(msg);
|
|
401
|
+
}, retryDelay * 1000);
|
|
402
|
+
if (done) done();
|
|
403
|
+
} else {
|
|
404
|
+
throw this.enhanceError(freshError, currentQueryString, currentQueryParams, "Query failed on fresh connection retry");
|
|
405
|
+
}
|
|
406
|
+
} finally {
|
|
407
|
+
if (freshConnection) await freshConnection.close();
|
|
394
408
|
}
|
|
395
|
-
}
|
|
396
|
-
|
|
409
|
+
} else {
|
|
410
|
+
throw firstAttemptError;
|
|
397
411
|
}
|
|
398
|
-
} else {
|
|
399
|
-
throw firstAttemptError;
|
|
400
412
|
}
|
|
401
413
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
414
|
+
} catch (err) {
|
|
415
|
+
const finalError = err instanceof Error ? err : new Error(String(err));
|
|
416
|
+
this.status({ fill: "red", shape: "ring", text: "query error" });
|
|
417
|
+
if (done) { done(finalError); } else { this.error(finalError, msg); }
|
|
418
|
+
}
|
|
407
419
|
};
|
|
420
|
+
|
|
421
|
+
// =================================================================
|
|
422
|
+
// FIN DE LA SECTION CORRIGÉE
|
|
423
|
+
// =================================================================
|
|
408
424
|
|
|
409
425
|
this.checkPool = async function (msg, send, done) {
|
|
410
426
|
try {
|
|
@@ -420,6 +436,10 @@ module.exports = function (RED) {
|
|
|
420
436
|
}, 1000);
|
|
421
437
|
return;
|
|
422
438
|
}
|
|
439
|
+
// S'assure que le pool est créé avant toute requête, y compris en streaming
|
|
440
|
+
if (!this.poolNode.pool) {
|
|
441
|
+
await this.poolNode.connect().then(c => c.close()); // Etablit le pool s'il n'existe pas
|
|
442
|
+
}
|
|
423
443
|
await this.runQuery(msg, send, done);
|
|
424
444
|
} catch (err) {
|
|
425
445
|
const finalError = err instanceof Error ? err : new Error(String(err));
|
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.1",
|
|
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",
|