@bkmj/node-red-contrib-odbcmj 2.1.2 → 2.1.3
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 +149 -167
- 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,215 @@ 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
|
// =================================================================
|
|
145
|
-
|
|
146
|
-
this.executeStreamQuery = async (queryString, queryParams, msg, send, done) => {
|
|
205
|
+
this.executeStreamQuery = async (dbConnection, queryString, queryParams, msg, send) => {
|
|
147
206
|
const chunkSize = parseInt(this.config.streamChunkSize) || 1;
|
|
207
|
+
const fetchSize = chunkSize > 50 ? 50 : chunkSize; // Optimisation : ne pas fetcher plus que nécessaire à la fois
|
|
148
208
|
let cursor;
|
|
149
|
-
let rowCount = 0;
|
|
150
|
-
let chunk = [];
|
|
151
209
|
|
|
152
210
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// CORRECTION : Obtenir la chaîne de connexion depuis le noeud de config
|
|
158
|
-
const connectionString = this.poolNode._buildConnectionString();
|
|
159
|
-
if (!connectionString) {
|
|
160
|
-
throw new Error("Impossible de construire une chaîne de connexion valide.");
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// CORRECTION : Appeler .cursor() comme une fonction de haut niveau du module odbc
|
|
164
|
-
cursor = await odbcModule.cursor(connectionString, queryString, queryParams);
|
|
165
|
-
|
|
211
|
+
// LA BONNE METHODE !
|
|
212
|
+
cursor = await dbConnection.query(queryString, queryParams, { cursor: true, fetchSize: fetchSize });
|
|
213
|
+
|
|
166
214
|
this.status({ fill: "blue", shape: "dot", text: "streaming rows..." });
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
215
|
+
|
|
216
|
+
let rowCount = 0;
|
|
217
|
+
let chunk = [];
|
|
218
|
+
let rows;
|
|
219
|
+
|
|
220
|
+
// .fetch() peut retourner plusieurs lignes à la fois, on boucle dessus
|
|
221
|
+
while ((rows = await cursor.fetch())) {
|
|
222
|
+
// Si fetch retourne un tableau vide, c'est la fin.
|
|
223
|
+
if (rows.length === 0) break;
|
|
224
|
+
|
|
225
|
+
for (const row of rows) {
|
|
226
|
+
rowCount++;
|
|
227
|
+
chunk.push(row);
|
|
228
|
+
if (chunk.length >= chunkSize) {
|
|
229
|
+
const newMsg = RED.util.cloneMessage(msg);
|
|
230
|
+
objPath.set(newMsg, this.config.outputObj, chunk);
|
|
231
|
+
newMsg.odbc_stream = { index: rowCount - chunk.length, count: chunk.length, complete: false };
|
|
232
|
+
send(newMsg);
|
|
233
|
+
chunk = [];
|
|
234
|
+
}
|
|
177
235
|
}
|
|
178
|
-
row = await cursor.fetch();
|
|
179
236
|
}
|
|
237
|
+
|
|
180
238
|
if (chunk.length > 0) {
|
|
181
239
|
const newMsg = RED.util.cloneMessage(msg);
|
|
182
240
|
objPath.set(newMsg, this.config.outputObj, chunk);
|
|
183
241
|
newMsg.odbc_stream = { index: rowCount - chunk.length, count: chunk.length, complete: true };
|
|
184
242
|
send(newMsg);
|
|
185
243
|
}
|
|
244
|
+
|
|
186
245
|
if (rowCount === 0) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
246
|
+
const newMsg = RED.util.cloneMessage(msg);
|
|
247
|
+
objPath.set(newMsg, this.config.outputObj, []);
|
|
248
|
+
newMsg.odbc_stream = { index: 0, count: 0, complete: true };
|
|
249
|
+
send(newMsg);
|
|
191
250
|
}
|
|
251
|
+
|
|
192
252
|
this.status({ fill: "green", shape: "dot", text: `success (${rowCount} rows)` });
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (cursor) await cursor.close();
|
|
253
|
+
|
|
254
|
+
} finally {
|
|
255
|
+
if (cursor) {
|
|
256
|
+
await cursor.close();
|
|
257
|
+
}
|
|
199
258
|
}
|
|
200
259
|
};
|
|
201
260
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
261
|
+
// =================================================================
|
|
262
|
+
// NOUVELLE LOGIQUE D'ENTREE UNIFIEE
|
|
263
|
+
// =================================================================
|
|
264
|
+
this.on("input", async (msg, send, done) => {
|
|
265
|
+
if (!this.poolNode) {
|
|
266
|
+
const err = new Error("ODBC Config node not properly configured.");
|
|
267
|
+
this.status({ fill: "red", shape: "ring", text: "No config node" });
|
|
268
|
+
done(err);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
208
271
|
|
|
272
|
+
let connection;
|
|
209
273
|
try {
|
|
210
274
|
this.status({ fill: "blue", shape: "dot", text: "preparing..." });
|
|
211
|
-
this.config.outputObj =
|
|
275
|
+
this.config.outputObj = this.config.outputObj || "payload";
|
|
212
276
|
|
|
277
|
+
// Obtenir la requête et les paramètres
|
|
213
278
|
const querySourceType = this.config.querySourceType || 'msg';
|
|
214
279
|
const querySource = this.config.querySource || 'query';
|
|
215
280
|
const paramsSourceType = this.config.paramsSourceType || 'msg';
|
|
216
281
|
const paramsSource = this.config.paramsSource || 'parameters';
|
|
217
282
|
|
|
218
|
-
|
|
219
|
-
RED.util.evaluateNodeProperty(paramsSource, paramsSourceType, this, msg, (err, value) =>
|
|
220
|
-
resolve(err ? undefined : value);
|
|
221
|
-
});
|
|
283
|
+
const currentQueryParams = await new Promise((resolve) => {
|
|
284
|
+
RED.util.evaluateNodeProperty(paramsSource, paramsSourceType, this, msg, (err, value) => resolve(err ? undefined : value));
|
|
222
285
|
});
|
|
223
286
|
|
|
224
287
|
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
|
-
});
|
|
288
|
+
RED.util.evaluateNodeProperty(querySource, querySourceType, this, msg, (err, value) => resolve(err ? undefined : (value || this.config.query || "")));
|
|
228
289
|
});
|
|
229
290
|
|
|
230
291
|
if (!currentQueryString) { throw new Error("No query to execute"); }
|
|
231
292
|
|
|
232
|
-
isPreparedStatement = currentQueryParams || (currentQueryString && currentQueryString.includes("?"));
|
|
293
|
+
const isPreparedStatement = currentQueryParams || (currentQueryString && currentQueryString.includes("?"));
|
|
233
294
|
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
295
|
currentQueryString = mustache.render(currentQueryString, msg);
|
|
240
296
|
}
|
|
297
|
+
|
|
298
|
+
// Obtenir une connexion du pool
|
|
299
|
+
this.status({ fill: "yellow", shape: "dot", text: "connecting..." });
|
|
300
|
+
connection = await this.poolNode.connect();
|
|
301
|
+
this.status({ fill: "blue", shape: "dot", text: "executing..." });
|
|
241
302
|
|
|
242
303
|
if (this.config.streaming) {
|
|
243
|
-
await this.executeStreamQuery(currentQueryString, currentQueryParams, msg, send
|
|
304
|
+
await this.executeStreamQuery(connection, currentQueryString, currentQueryParams, msg, send);
|
|
244
305
|
} 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
|
-
}
|
|
306
|
+
const newMsg = await this.executeQueryAndProcess(connection, currentQueryString, currentQueryParams, msg);
|
|
307
|
+
this.status({ fill: "green", shape: "dot", text: "success" });
|
|
308
|
+
send(newMsg);
|
|
300
309
|
}
|
|
310
|
+
|
|
311
|
+
// Si tout s'est bien passé, on appelle done() sans erreur
|
|
312
|
+
done();
|
|
313
|
+
|
|
301
314
|
} catch (err) {
|
|
302
|
-
const finalError = err
|
|
315
|
+
const finalError = this.enhanceError(err, null, null, "Query Execution Failed");
|
|
303
316
|
this.status({ fill: "red", shape: "ring", text: "query error" });
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
};
|
|
317
|
+
done(finalError); // On passe l'erreur à done() pour que Node-RED la gère
|
|
307
318
|
|
|
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());
|
|
319
|
+
} finally {
|
|
320
|
+
if (connection) {
|
|
321
|
+
try {
|
|
322
|
+
await connection.close();
|
|
323
|
+
} catch (closeErr) {
|
|
324
|
+
this.warn(`Failed to close DB connection: ${closeErr.message}`);
|
|
325
|
+
}
|
|
337
326
|
}
|
|
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
327
|
}
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
this.on("input", async (msg, send, done) => {
|
|
347
|
-
// ... (Pas de changement dans cette section)
|
|
348
328
|
});
|
|
349
|
-
|
|
350
|
-
this.on("close",
|
|
351
|
-
|
|
329
|
+
|
|
330
|
+
this.on("close", (done) => {
|
|
331
|
+
this.status({});
|
|
332
|
+
// La logique de fermeture du pool est déjà dans le noeud de config
|
|
333
|
+
done();
|
|
352
334
|
});
|
|
353
335
|
|
|
354
336
|
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.3",
|
|
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",
|