node-red-contrib-questdb 0.6.6 → 0.6.9
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.
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
const typeSelect = $('<select/>', {class: "column-type"})
|
|
101
101
|
.css({width: "20%"})
|
|
102
102
|
.appendTo(row);
|
|
103
|
-
['auto', 'float', 'double', 'integer', 'long', 'decimal', 'string', 'boolean', 'timestamp', 'array'].forEach(function(t) {
|
|
103
|
+
['auto', 'float', 'double', 'integer', 'long', 'decimal', 'varchar', 'string', 'boolean', 'timestamp', 'array'].forEach(function(t) {
|
|
104
104
|
$('<option/>').val(t).text(t).appendTo(typeSelect);
|
|
105
105
|
});
|
|
106
106
|
typeSelect.val(data.type || "auto");
|
|
@@ -211,7 +211,8 @@
|
|
|
211
211
|
<li><b>integer</b> - 32-bit signed integer</li>
|
|
212
212
|
<li><b>long</b> - 64-bit signed integer</li>
|
|
213
213
|
<li><b>decimal</b> - Arbitrary precision decimal</li>
|
|
214
|
-
<li><b>
|
|
214
|
+
<li><b>varchar</b> - Variable-length text (QuestDB native type, preferred over string)</li>
|
|
215
|
+
<li><b>string</b> - Text value (alias for varchar)</li>
|
|
215
216
|
<li><b>boolean</b> - true/false</li>
|
|
216
217
|
<li><b>timestamp</b> - Date/time value</li>
|
|
217
218
|
<li><b>array</b> - Array of doubles (double[])</li>
|
package/nodes/questdb-mapper.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
name: {value:""}
|
|
12
12
|
},
|
|
13
13
|
inputs:1,
|
|
14
|
-
outputs:
|
|
15
|
-
outputLabels: ["
|
|
14
|
+
outputs:5,
|
|
15
|
+
outputLabels: ["long", "double", "bool", "string", "unresolvable"],
|
|
16
16
|
icon: "font-awesome/fa-random",
|
|
17
17
|
paletteLabel: "Type Router",
|
|
18
18
|
label: function() { return this.name || "Type Router"; },
|
|
@@ -29,20 +29,25 @@
|
|
|
29
29
|
|
|
30
30
|
<script type="text/html" data-help-name="questdb-type-router">
|
|
31
31
|
<p>Routes messages to different outputs based on payload data type.</p>
|
|
32
|
+
|
|
32
33
|
<h3>Outputs</h3>
|
|
33
34
|
<ol class="node-ports">
|
|
34
|
-
<li><b>
|
|
35
|
-
<li><b>
|
|
35
|
+
<li><b>Long:</b> Integer values (int and long both map here)</li>
|
|
36
|
+
<li><b>Double:</b> Floating-point values (float and double both map here)</li>
|
|
36
37
|
<li><b>Bool:</b> Boolean values (including "true"/"false" strings)</li>
|
|
37
|
-
<li><b>String:</b> String values</li>
|
|
38
|
+
<li><b>String:</b> String values that are not numbers or booleans</li>
|
|
39
|
+
<li><b>Unresolvable:</b> Values whose type cannot be determined (objects, arrays, null, NaN, Infinity)</li>
|
|
38
40
|
</ol>
|
|
41
|
+
|
|
39
42
|
<h3>Type Detection</h3>
|
|
40
43
|
<ul>
|
|
41
|
-
<li>
|
|
42
|
-
<li>
|
|
43
|
-
<li>
|
|
44
|
-
<li>String
|
|
44
|
+
<li><b>Bool:</b> <code>true</code>, <code>false</code>, or strings <code>"true"</code>/<code>"false"</code> (case-insensitive)</li>
|
|
45
|
+
<li><b>Long:</b> Whole numbers or numeric strings without a decimal point (e.g. <code>42</code>, <code>"42"</code>)</li>
|
|
46
|
+
<li><b>Double:</b> Numbers with a decimal point or numeric strings with <code>.</code> (e.g. <code>3.14</code>, <code>"3.14"</code>)</li>
|
|
47
|
+
<li><b>String:</b> Non-numeric, non-boolean strings</li>
|
|
48
|
+
<li><b>Unresolvable:</b> Objects, arrays, <code>null</code>, <code>NaN</code>, <code>Infinity</code></li>
|
|
45
49
|
</ul>
|
|
50
|
+
|
|
46
51
|
<h3>Conversion</h3>
|
|
47
52
|
<p>String representations of numbers and booleans are automatically converted to their native types.</p>
|
|
48
53
|
</script>
|
|
@@ -1,71 +1,96 @@
|
|
|
1
|
-
module.exports = function(RED) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
function TypeRouterNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
var node = this;
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// Detect the canonical type from a value.
|
|
7
|
+
// Returns 'long' | 'double' | 'bool' | 'string' | null (unresolvable)
|
|
8
|
+
function detectType(value) {
|
|
9
|
+
if (typeof value === 'boolean') return 'bool';
|
|
10
|
+
if (typeof value === 'number') {
|
|
11
|
+
if (!isFinite(value)) return null;
|
|
12
|
+
return Number.isInteger(value) ? 'long' : 'double';
|
|
13
|
+
}
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
var trimmed = value.trim();
|
|
16
|
+
var lower = trimmed.toLowerCase();
|
|
17
|
+
if (lower === 'true' || lower === 'false') return 'bool';
|
|
18
|
+
if (trimmed !== '' && !isNaN(trimmed)) {
|
|
19
|
+
return trimmed.includes('.') ? 'double' : 'long';
|
|
20
|
+
}
|
|
21
|
+
return 'string';
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
8
25
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
// Cast value to the required type.
|
|
27
|
+
// Returns the casted value, or null if the cast is not possible.
|
|
28
|
+
function castToType(value, type) {
|
|
29
|
+
var num, t;
|
|
30
|
+
if (type === 'long') {
|
|
31
|
+
if (typeof value === 'boolean') return null;
|
|
32
|
+
num = Number(value);
|
|
33
|
+
if (!isFinite(num) || !Number.isInteger(num)) return null;
|
|
34
|
+
return num;
|
|
35
|
+
}
|
|
36
|
+
if (type === 'double') {
|
|
37
|
+
if (typeof value === 'boolean') return null;
|
|
38
|
+
num = Number(value);
|
|
39
|
+
if (!isFinite(num)) return null;
|
|
40
|
+
return num;
|
|
41
|
+
}
|
|
42
|
+
if (type === 'bool') {
|
|
43
|
+
if (typeof value === 'boolean') return value;
|
|
44
|
+
if (typeof value === 'string') {
|
|
45
|
+
t = value.trim().toLowerCase();
|
|
46
|
+
if (t === 'true') return true;
|
|
47
|
+
if (t === 'false') return false;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (type === 'string') {
|
|
52
|
+
if (typeof value === 'string') return value;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
27
57
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
msg.dataType = 'bool';
|
|
31
|
-
node.status({ fill: "purple", shape: "dot", text: "bool: true" });
|
|
32
|
-
return node.send([null, null, msg, null]);
|
|
33
|
-
}
|
|
34
|
-
else if (trimmed.toLowerCase() === 'false') {
|
|
35
|
-
msg.payload = false;
|
|
36
|
-
msg.dataType = 'bool';
|
|
37
|
-
node.status({ fill: "purple", shape: "dot", text: "bool: false" });
|
|
38
|
-
return node.send([null, null, msg, null]);
|
|
39
|
-
}
|
|
40
|
-
else if (trimmed !== '' && !isNaN(trimmed)) {
|
|
41
|
-
var num = Number(trimmed);
|
|
42
|
-
msg.payload = num;
|
|
43
|
-
if (Number.isInteger(num) && !trimmed.includes('.')) {
|
|
44
|
-
msg.dataType = 'int';
|
|
45
|
-
node.status({ fill: "blue", shape: "dot", text: "int: " + num });
|
|
46
|
-
return node.send([msg, null, null, null]);
|
|
47
|
-
} else {
|
|
48
|
-
msg.dataType = 'float';
|
|
49
|
-
node.status({ fill: "green", shape: "dot", text: "float: " + num.toFixed(2) });
|
|
50
|
-
return node.send([null, msg, null, null]);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
msg.payload = String(value);
|
|
55
|
-
msg.dataType = 'string';
|
|
56
|
-
node.status({ fill: "yellow", shape: "dot", text: "str: " + (value.length > 10 ? value.substring(0,10) + "..." : value) });
|
|
57
|
-
return node.send([null, null, null, msg]);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
msg.payload = String(value);
|
|
62
|
-
msg.dataType = 'string';
|
|
63
|
-
node.status({ fill: "yellow", shape: "dot", text: "str: (converted)" });
|
|
64
|
-
return node.send([null, null, null, msg]);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
58
|
+
var TYPE_OUTPUT = { long: 0, double: 1, bool: 2, string: 3 };
|
|
59
|
+
var TYPE_COLOR = { long: 'blue', double: 'green', bool: 'purple', string: 'yellow' };
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
node.on('input', function (msg) {
|
|
62
|
+
var value = msg.payload;
|
|
63
|
+
|
|
64
|
+
var type = detectType(value);
|
|
65
|
+
if (type === null) {
|
|
66
|
+
node.status({ fill: 'red', shape: 'ring', text: 'unresolvable' });
|
|
67
|
+
return node.send([null, null, null, null, msg]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var castedValue = castToType(value, type);
|
|
71
|
+
if (castedValue === null) {
|
|
72
|
+
node.status({ fill: 'red', shape: 'ring', text: 'cast error' });
|
|
73
|
+
return node.send([null, null, null, null, msg]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
msg.payload = castedValue;
|
|
77
|
+
msg.dataType = type;
|
|
78
|
+
|
|
79
|
+
var outputIndex = TYPE_OUTPUT[type];
|
|
80
|
+
var outputs = [null, null, null, null, null];
|
|
81
|
+
outputs[outputIndex] = msg;
|
|
82
|
+
|
|
83
|
+
var label = String(castedValue);
|
|
84
|
+
if (label.length > 12) label = label.substring(0, 12) + '...';
|
|
85
|
+
node.status({ fill: TYPE_COLOR[type], shape: 'dot', text: type + ': ' + label });
|
|
86
|
+
|
|
87
|
+
return node.send(outputs);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
node.on('close', function () {
|
|
91
|
+
node.status({});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
RED.nodes.registerType('questdb-type-router', TypeRouterNode);
|
|
71
96
|
};
|
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.9",
|
|
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"
|