@bkmj/node-red-contrib-odbcmj 2.2.0 → 2.2.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.
Files changed (3) hide show
  1. package/odbc.html +40 -10
  2. package/odbc.js +61 -11
  3. package/package.json +1 -1
package/odbc.html CHANGED
@@ -71,12 +71,36 @@
71
71
 
72
72
  $('#node-config-test-connection').on('click', function() {
73
73
  var button = $(this);
74
+ var connectionMode = $("#node-config-input-connectionMode").val();
75
+
76
+ if (connectionMode === 'structured') {
77
+ var server = $("#node-config-input-server").val().trim();
78
+ if (!server) {
79
+ RED.notify("Le champ 'Server' est requis pour le test.", {type: "warning", timeout: 3000});
80
+ return;
81
+ }
82
+ } else {
83
+ var connStr = $("#node-config-input-connectionString").val().trim();
84
+ if (!connStr) {
85
+ RED.notify("La chaîne de connexion est requise pour le test.", {type: "warning", timeout: 3000});
86
+ return;
87
+ }
88
+ // Nouvelle validation :
89
+ // Elle est invalide SEULEMENT si elle ne contient PAS de DSN ET qu'elle ne respecte PAS l'ancien critère pour les chaînes DSN-less.
90
+ var isDsnString = /DSN=[^;]+/i.test(connStr);
91
+ var isDriverBasedString = /DRIVER=\{.+?\}/i.test(connStr) && /(SERVER|DATABASE|UID|PWD)=/i.test(connStr); // Un peu plus complet
92
+
93
+ if (!isDsnString && !isDriverBasedString) {
94
+ RED.notify("La chaîne de connexion semble invalide ou incomplète (ex: DSN=value; ou DRIVER={...};SERVER=...;).", {type: "warning", timeout: 4000});
95
+ return;
96
+ }
97
+ }
98
+
74
99
  var originalText = "Test Connection";
75
100
  var icon = button.find("i");
76
101
  icon.removeClass('fa-bolt').addClass('fa-spinner fa-spin');
77
102
  button.text(' Testing...').prop('disabled', true);
78
103
 
79
- var connectionMode = $("#node-config-input-connectionMode").val();
80
104
  var configData = {
81
105
  connectionMode: connectionMode,
82
106
  dbType: $("#node-config-input-dbType").val(),
@@ -272,7 +296,6 @@
272
296
  outputObj: { value: "payload" },
273
297
  streaming: { value: false },
274
298
  streamChunkSize: { value: 1, validate: RED.validators.number() },
275
- // Nouveaux `defaults` pour les Typed Inputs
276
299
  querySource: { value: "query", required: false },
277
300
  querySourceType: { value: "msg", required: false },
278
301
  paramsSource: { value: "parameters", required: false },
@@ -295,7 +318,6 @@
295
318
  $(".stream-options").toggle(this.checked);
296
319
  }).trigger("change");
297
320
 
298
- // Initialisation des Typed Inputs
299
321
  $("#node-input-querySource").typedInput({
300
322
  default: 'msg',
301
323
  typeField: "#node-input-querySourceType",
@@ -414,7 +436,7 @@ Executes a query against a configured ODBC data source.
414
436
  ### Properties
415
437
 
416
438
  - **Connection**: The `odbc config` node to use.
417
- - **Query (fallback)**: A static SQL query to run if the "Query Source" does not provide one. Can contain Mustache syntax (e.g., `{{{payload.id}}}`).
439
+ - **Query (fallback)**: A static SQL query to run if the "Query (fallback)" does not provide one. Can contain Mustache syntax (e.g., `{{{payload.id}}}`).
418
440
  - **Result to**: The `msg` property where the query result will be stored. Default: `payload`.
419
441
 
420
442
  ### Streaming Results
@@ -425,10 +447,18 @@ For queries that return a large number of rows, streaming prevents high memory u
425
447
  - **`Chunk Size`**: The number of rows to include in each output message. A value of `1` means one message will be sent for every single row.
426
448
 
427
449
  #### Streaming Output Format
428
- When streaming is active, each output message will contain:
429
- - A payload (or the configured output property) containing an array of rows for the current chunk.
430
- - A `msg.odbc_stream` object with metadata for tracking progress:
431
- - `index`: The starting index of the current chunk (e.g., 0, 100, 200...).
432
- - `count`: The number of rows in the current chunk.
433
- - `complete`: A boolean that is `true` only on the very last message of the stream, useful for triggering a final action. The last payload will always be an empty array.
450
+ When streaming is active, the node sends messages in sequence:
451
+
452
+ 1. **Data Messages**: One or more messages where the payload (or the configured output property) contains an array of rows for the current chunk. For these messages, `msg.odbc_stream.complete` will be **`false`**.
453
+
454
+ 2. **Completion Message**: A single, final message indicating the end of the stream. For this message:
455
+ - The payload will be an **empty array `[]`**.
456
+ - `msg.odbc_stream.complete` will be **`true`**.
457
+
458
+ The `msg.odbc_stream` object contains metadata for tracking:
459
+ - `index`: The starting index of the current chunk. For the completion message, this will be the total number of rows processed.
460
+ - `count`: The number of rows in the chunk. This will be `0` for the completion message.
461
+ - `complete`: The boolean flag (`true`/`false`).
462
+
463
+ This pattern ensures you can reliably trigger a final action (like closing a file or calculating an aggregate) only when the message with `complete: true` is received.
434
464
  </script>
package/odbc.js CHANGED
@@ -12,7 +12,6 @@ module.exports = function (RED) {
12
12
 
13
13
  this.credentials = RED.nodes.getCredentials(this.id);
14
14
 
15
- // Cette fonction est maintenant cruciale pour le mode streaming
16
15
  this._buildConnectionString = function() {
17
16
  if (this.config.connectionMode === 'structured') {
18
17
  if (!this.config.dbType || !this.config.server) {
@@ -119,9 +118,61 @@ module.exports = function (RED) {
119
118
  });
120
119
 
121
120
  RED.httpAdmin.post("/odbc_config/:id/test", RED.auth.needsPermission("odbc.write"), async function(req, res) {
122
- // ... (Pas de changement dans cette section)
123
- });
121
+ const tempConfig = req.body;
124
122
 
123
+ const buildTestConnectionString = () => {
124
+ if (tempConfig.connectionMode === 'structured') {
125
+ if (!tempConfig.dbType || !tempConfig.server) {
126
+ throw new Error("En mode structuré, le type de base de données et le serveur sont requis.");
127
+ }
128
+ let driver;
129
+ let parts = [];
130
+ switch (tempConfig.dbType) {
131
+ case 'sqlserver': driver = 'ODBC Driver 17 for SQL Server'; break;
132
+ case 'postgresql': driver = 'PostgreSQL Unicode'; break;
133
+ case 'mysql': driver = 'MySQL ODBC 8.0 Unicode Driver'; break;
134
+ default: driver = tempConfig.driver || ''; break;
135
+ }
136
+ if(driver) parts.unshift(`DRIVER={${driver}}`);
137
+ parts.push(`SERVER=${tempConfig.server}`);
138
+ if (tempConfig.database) parts.push(`DATABASE=${tempConfig.database}`);
139
+ if (tempConfig.user) parts.push(`UID=${tempConfig.user}`);
140
+ if (tempConfig.password) parts.push(`PWD=${tempConfig.password}`);
141
+ return parts.join(';');
142
+ } else {
143
+ let connStr = tempConfig.connectionString || "";
144
+ if (!connStr) {
145
+ throw new Error("La chaîne de connexion ne peut pas être vide.");
146
+ }
147
+ return connStr;
148
+ }
149
+ };
150
+
151
+ let connection;
152
+ try {
153
+ const testConnectionString = buildTestConnectionString();
154
+
155
+ // ==============================================================
156
+ // LIGNE DE DÉBOGAGE AJOUTÉE
157
+ // ==============================================================
158
+ console.log("[ODBC Test] Attempting to connect with string:", testConnectionString);
159
+ // ==============================================================
160
+
161
+ const connectionOptions = {
162
+ connectionString: testConnectionString,
163
+ loginTimeout: 10
164
+ };
165
+ connection = await odbcModule.connect(connectionOptions);
166
+ res.sendStatus(200);
167
+ } catch (err) {
168
+ console.error("[ODBC Test] Connection failed:", err); // Ajout d'un log d'erreur
169
+ res.status(500).send(err.message || "Erreur inconnue durant le test.");
170
+ } finally {
171
+ if (connection) {
172
+ await connection.close();
173
+ }
174
+ }
175
+ });
125
176
 
126
177
  // --- ODBC Query Node ---
127
178
  function odbc(config) {
@@ -130,7 +181,6 @@ module.exports = function (RED) {
130
181
  this.poolNode = RED.nodes.getNode(this.config.connection);
131
182
  this.name = this.config.name;
132
183
 
133
- // ... (enhanceError et executeQueryAndProcess restent inchangés)
134
184
  this.enhanceError = (error, query, params, defaultMessage = "Query error") => {
135
185
  const queryContext = (() => {
136
186
  let s = "";
@@ -195,10 +245,10 @@ module.exports = function (RED) {
195
245
  if (Object.keys(otherParams).length) { newMsg.odbc = otherParams; }
196
246
  return newMsg;
197
247
  };
198
-
248
+
199
249
  this.executeStreamQuery = async (dbConnection, queryString, queryParams, msg, send) => {
200
250
  const chunkSize = parseInt(this.config.streamChunkSize) || 1;
201
- const fetchSize = chunkSize > 100 ? 100 : chunkSize; // Optimisation du fetch
251
+ const fetchSize = chunkSize > 100 ? 100 : chunkSize;
202
252
  let cursor;
203
253
 
204
254
  try {
@@ -252,7 +302,7 @@ module.exports = function (RED) {
252
302
  if (!this.poolNode) {
253
303
  return done(new Error("ODBC Config node not properly configured."));
254
304
  }
255
-
305
+
256
306
  const execute = async (connection) => {
257
307
  this.config.outputObj = this.config.outputObj || "payload";
258
308
 
@@ -265,7 +315,7 @@ module.exports = function (RED) {
265
315
  let query = await new Promise(resolve => RED.util.evaluateNodeProperty(querySource, querySourceType, this, msg, (err, val) => resolve(err ? undefined : (val || this.config.query || ""))));
266
316
 
267
317
  if (!query) throw new Error("No query to execute");
268
-
318
+
269
319
  const isPreparedStatement = params || (query && query.includes("?"));
270
320
  if (!isPreparedStatement && query) {
271
321
  query = mustache.render(query, msg);
@@ -280,7 +330,7 @@ module.exports = function (RED) {
280
330
  send(newMsg);
281
331
  }
282
332
  };
283
-
333
+
284
334
  let connectionFromPool;
285
335
  try {
286
336
  this.status({ fill: "yellow", shape: "dot", text: "connecting..." });
@@ -320,7 +370,7 @@ module.exports = function (RED) {
320
370
  this.status({});
321
371
  done();
322
372
  });
323
-
373
+
324
374
  if (this.poolNode) {
325
375
  this.status({ fill: "green", shape: "dot", text: "ready" });
326
376
  } else {
@@ -328,6 +378,6 @@ module.exports = function (RED) {
328
378
  this.warn("ODBC Config node not found or not deployed.");
329
379
  }
330
380
  }
331
-
381
+
332
382
  RED.nodes.registerType("odbc", odbc);
333
383
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bkmj/node-red-contrib-odbcmj",
3
- "version": "2.2.0",
3
+ "version": "2.2.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",