@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.
- package/odbc.html +40 -10
- package/odbc.js +61 -11
- 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
|
|
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,
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
-
|
|
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
|
-
|
|
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;
|
|
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.
|
|
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",
|