node-red-contrib-questdb 0.6.22 → 0.6.24
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/nodes/questdb-value.js +38 -3
- package/nodes/questdb.js +77 -27
- package/package.json +1 -1
package/nodes/questdb-value.js
CHANGED
|
@@ -110,14 +110,30 @@ module.exports = function (RED) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
node.on('input', async function (msg) {
|
|
113
|
-
// Connection checks
|
|
113
|
+
// Connection checks (rate-limit error logging to once per 5s)
|
|
114
114
|
if (!connection.connected && !connection.connecting) {
|
|
115
115
|
connection.connect();
|
|
116
|
-
|
|
116
|
+
var now = Date.now();
|
|
117
|
+
if (now - connection.lastNotConnectedLog > 5000) {
|
|
118
|
+
connection.lastNotConnectedLog = now;
|
|
119
|
+
sendError(msg, 'Not connected to QuestDB, reconnecting...');
|
|
120
|
+
} else {
|
|
121
|
+
var errMsg = RED.util.cloneMessage(msg);
|
|
122
|
+
errMsg.payload = { success: false, error: 'Not connected to QuestDB, reconnecting...' };
|
|
123
|
+
node.send([null, errMsg]);
|
|
124
|
+
}
|
|
117
125
|
return;
|
|
118
126
|
}
|
|
119
127
|
if (!connection.connected || !connection.sender) {
|
|
120
|
-
|
|
128
|
+
var now2 = Date.now();
|
|
129
|
+
if (now2 - connection.lastNotConnectedLog > 5000) {
|
|
130
|
+
connection.lastNotConnectedLog = now2;
|
|
131
|
+
sendError(msg, 'Not connected to QuestDB');
|
|
132
|
+
} else {
|
|
133
|
+
var errMsg2 = RED.util.cloneMessage(msg);
|
|
134
|
+
errMsg2.payload = { success: false, error: 'Not connected to QuestDB' };
|
|
135
|
+
node.send([null, errMsg2]);
|
|
136
|
+
}
|
|
121
137
|
return;
|
|
122
138
|
}
|
|
123
139
|
if (
|
|
@@ -213,6 +229,25 @@ module.exports = function (RED) {
|
|
|
213
229
|
connection.sender.at(timestampMicros);
|
|
214
230
|
}
|
|
215
231
|
|
|
232
|
+
// Track rows and flush at threshold
|
|
233
|
+
connection.rowCount++;
|
|
234
|
+
if (connection.flushRows && connection.rowCount >= connection.flushRows) {
|
|
235
|
+
try {
|
|
236
|
+
await connection.sender.flush();
|
|
237
|
+
connection.rowCount = 0;
|
|
238
|
+
} catch (flushErr) {
|
|
239
|
+
var flushErrMsg = formatError(flushErr, { operation: 'flush', table: tableName });
|
|
240
|
+
RED.log.error('[QuestDB] Flush failed: ' + flushErrMsg);
|
|
241
|
+
if (isDisconnectionError(flushErr)) {
|
|
242
|
+
connection.updateState(false, flushErr.message);
|
|
243
|
+
connection.sender = null;
|
|
244
|
+
connection.connect();
|
|
245
|
+
}
|
|
246
|
+
sendError(msg, flushErrMsg);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
216
251
|
var label = type + ': ' + String(castedValue);
|
|
217
252
|
if (label.length > 25) label = label.substring(0, 25) + '...';
|
|
218
253
|
node.status({ fill: 'green', shape: 'dot', text: tableName + ' ' + label });
|
package/nodes/questdb.js
CHANGED
|
@@ -31,17 +31,9 @@ module.exports = function (RED) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} else {
|
|
38
|
-
if (configNode.autoFlushRows) {
|
|
39
|
-
connStr += `auto_flush_rows=${configNode.autoFlushRows};`;
|
|
40
|
-
}
|
|
41
|
-
if (configNode.autoFlushInterval) {
|
|
42
|
-
connStr += `auto_flush_interval=${configNode.autoFlushInterval};`;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
34
|
+
// Always disable sender's built-in auto-flush to prevent uncaught exceptions.
|
|
35
|
+
// We run our own flush timer with error handling in the connection state.
|
|
36
|
+
connStr += `auto_flush=off;`;
|
|
45
37
|
|
|
46
38
|
// Buffer options
|
|
47
39
|
if (configNode.initBufSize) {
|
|
@@ -110,8 +102,11 @@ module.exports = function (RED) {
|
|
|
110
102
|
connecting: false,
|
|
111
103
|
users: 0,
|
|
112
104
|
reconnectTimer: null,
|
|
105
|
+
flushTimer: null,
|
|
106
|
+
rowCount: 0,
|
|
113
107
|
emitter: new EventEmitter(),
|
|
114
108
|
lastError: null,
|
|
109
|
+
lastNotConnectedLog: 0,
|
|
115
110
|
|
|
116
111
|
// Method to update state and emit event
|
|
117
112
|
updateState: function (newStatus, error = null) {
|
|
@@ -171,6 +166,32 @@ module.exports = function (RED) {
|
|
|
171
166
|
|
|
172
167
|
connectionState.updateState(true);
|
|
173
168
|
|
|
169
|
+
// Start managed flush timer (replaces sender's built-in auto-flush)
|
|
170
|
+
if (connectionState.flushTimer) {
|
|
171
|
+
clearInterval(connectionState.flushTimer);
|
|
172
|
+
}
|
|
173
|
+
const flushInterval = configNode.autoFlushInterval || 1000;
|
|
174
|
+
connectionState.flushTimer = setInterval(async () => {
|
|
175
|
+
if (!connectionState.connected || !connectionState.sender) return;
|
|
176
|
+
if (connectionState.rowCount === 0) return;
|
|
177
|
+
try {
|
|
178
|
+
await connectionState.sender.flush();
|
|
179
|
+
connectionState.rowCount = 0;
|
|
180
|
+
} catch (flushErr) {
|
|
181
|
+
const flushErrMsg = formatError(flushErr, { operation: 'auto-flush' });
|
|
182
|
+
RED.log.error(`[QuestDB] Flush failed: ${flushErrMsg}`);
|
|
183
|
+
connectionState.emitter.emit('flushError', flushErr);
|
|
184
|
+
if (isDisconnectionError(flushErr)) {
|
|
185
|
+
connectionState.updateState(false, flushErr.message);
|
|
186
|
+
connectionState.sender = null;
|
|
187
|
+
connectionState.connect();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}, flushInterval);
|
|
191
|
+
|
|
192
|
+
// Also flush when row count threshold is reached
|
|
193
|
+
connectionState.flushRows = configNode.autoFlushRows || 500;
|
|
194
|
+
|
|
174
195
|
RED.log.info(
|
|
175
196
|
`[QuestDB] Connected to ${configNode.protocol}://${configNode.host}:${configNode.port}`
|
|
176
197
|
);
|
|
@@ -196,6 +217,11 @@ module.exports = function (RED) {
|
|
|
196
217
|
};
|
|
197
218
|
|
|
198
219
|
connectionState.disconnect = async function () {
|
|
220
|
+
if (connectionState.flushTimer) {
|
|
221
|
+
clearInterval(connectionState.flushTimer);
|
|
222
|
+
connectionState.flushTimer = null;
|
|
223
|
+
}
|
|
224
|
+
|
|
199
225
|
if (connectionState.reconnectTimer) {
|
|
200
226
|
clearTimeout(connectionState.reconnectTimer);
|
|
201
227
|
connectionState.reconnectTimer = null;
|
|
@@ -310,15 +336,32 @@ module.exports = function (RED) {
|
|
|
310
336
|
node.send([null, errMsg]);
|
|
311
337
|
}
|
|
312
338
|
|
|
313
|
-
// Auto-reconnect if disconnected
|
|
339
|
+
// Auto-reconnect if disconnected (rate-limit error logging to once per 5s)
|
|
314
340
|
if (!connection.connected && !connection.connecting) {
|
|
315
341
|
connection.connect();
|
|
316
|
-
|
|
342
|
+
var now = Date.now();
|
|
343
|
+
if (now - connection.lastNotConnectedLog > 5000) {
|
|
344
|
+
connection.lastNotConnectedLog = now;
|
|
345
|
+
sendError('Not connected to QuestDB, reconnecting...');
|
|
346
|
+
} else {
|
|
347
|
+
// Still send to error output but don't log
|
|
348
|
+
var errMsg = RED.util.cloneMessage(msg);
|
|
349
|
+
errMsg.payload = { success: false, error: 'Not connected to QuestDB, reconnecting...' };
|
|
350
|
+
node.send([null, errMsg]);
|
|
351
|
+
}
|
|
317
352
|
return;
|
|
318
353
|
}
|
|
319
354
|
|
|
320
355
|
if (!connection.connected || !connection.sender) {
|
|
321
|
-
|
|
356
|
+
var now2 = Date.now();
|
|
357
|
+
if (now2 - connection.lastNotConnectedLog > 5000) {
|
|
358
|
+
connection.lastNotConnectedLog = now2;
|
|
359
|
+
sendError('Not connected to QuestDB');
|
|
360
|
+
} else {
|
|
361
|
+
var errMsg2 = RED.util.cloneMessage(msg);
|
|
362
|
+
errMsg2.payload = { success: false, error: 'Not connected to QuestDB' };
|
|
363
|
+
node.send([null, errMsg2]);
|
|
364
|
+
}
|
|
322
365
|
return;
|
|
323
366
|
}
|
|
324
367
|
|
|
@@ -485,19 +528,7 @@ module.exports = function (RED) {
|
|
|
485
528
|
} else if (typeof value === 'boolean') {
|
|
486
529
|
connection.sender.booleanColumn(key, value);
|
|
487
530
|
} else if (typeof value === 'string') {
|
|
488
|
-
|
|
489
|
-
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
|
|
490
|
-
const dateValue = new Date(value);
|
|
491
|
-
if (!isNaN(dateValue.getTime())) {
|
|
492
|
-
// Convert to microseconds for QuestDB
|
|
493
|
-
const microSeconds = BigInt(dateValue.getTime()) * 1000n;
|
|
494
|
-
connection.sender.timestampColumn(key, microSeconds);
|
|
495
|
-
} else {
|
|
496
|
-
connection.sender.stringColumn(key, value);
|
|
497
|
-
}
|
|
498
|
-
} else {
|
|
499
|
-
connection.sender.stringColumn(key, value);
|
|
500
|
-
}
|
|
531
|
+
connection.sender.stringColumn(key, value);
|
|
501
532
|
} else if (value instanceof Date) {
|
|
502
533
|
// Convert to microseconds for QuestDB
|
|
503
534
|
const microSeconds = BigInt(value.getTime()) * 1000n;
|
|
@@ -540,6 +571,25 @@ module.exports = function (RED) {
|
|
|
540
571
|
}
|
|
541
572
|
}
|
|
542
573
|
|
|
574
|
+
// Track rows and flush at threshold
|
|
575
|
+
connection.rowCount++;
|
|
576
|
+
if (connection.flushRows && connection.rowCount >= connection.flushRows) {
|
|
577
|
+
try {
|
|
578
|
+
await connection.sender.flush();
|
|
579
|
+
connection.rowCount = 0;
|
|
580
|
+
} catch (flushErr) {
|
|
581
|
+
const flushErrMsg = formatError(flushErr, { operation: 'flush', table: tableName });
|
|
582
|
+
RED.log.error(`[QuestDB] Flush failed: ${flushErrMsg}`);
|
|
583
|
+
if (isDisconnectionError(flushErr)) {
|
|
584
|
+
connection.updateState(false, flushErr.message);
|
|
585
|
+
connection.sender = null;
|
|
586
|
+
connection.connect();
|
|
587
|
+
}
|
|
588
|
+
sendError(flushErrMsg);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
543
593
|
node.status({ fill: 'green', shape: 'dot', text: `sent: ${tableName}` });
|
|
544
594
|
msg.payload = { success: true, table: tableName };
|
|
545
595
|
node.send([msg, null]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-questdb",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.24",
|
|
4
4
|
"description": "Node-RED nodes for writing high-performance time-series data to QuestDB using Influx Line Protocol (ILP). Supports IoT, industrial monitoring, smart buildings, fleet telematics, healthcare, agriculture, and more.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Holger Amort"
|