@bkmj/node-red-contrib-odbcmj 1.2.4 → 1.3.0
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 +58 -55
- package/odbc.html +250 -209
- package/odbc.js +263 -216
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,39 +1,38 @@
|
|
|
1
1
|
# node-red-contrib-odbcmj
|
|
2
2
|
|
|
3
|
-
A Node
|
|
3
|
+
A Node-RED implementation of odbc.js (https://www.npmjs.com/package/odbc). This node allows you to make queries to a database through an ODBC connection. Additionally, parameters can be passed to the SQL query using Mustache syntax, prepared statements, or directly in the query string.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
|
-
##
|
|
6
|
+
## Acknowledgment
|
|
7
7
|
|
|
8
|
-
This node is an unofficial fork of node-red-contrib-odbc
|
|
8
|
+
This node is an unofficial fork of node-red-contrib-odbc by Mark Irish (https://github.com/markdirish/node-red-contrib-odbc) and is vastly inspired by it. It also takes ideas from node-red-contrib-odbc2 by AIS Automation (https://github.com/AISAutomation/node-red-contrib-odbc2).
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- Warnings when mustache will render an undefined variable.
|
|
12
|
-
- Fixes the output field option so that nested object can be used.
|
|
13
|
-
- Fixes the checkbox for the pool shrink option.
|
|
14
|
-
- Uses ace/mode/sql for the SQL input field.
|
|
15
|
-
- Connection nodes can have individually defined names
|
|
16
|
-
- Selectable SQL syntax checker
|
|
10
|
+
**Overall changes:**
|
|
17
11
|
|
|
12
|
+
* Can use Mustache as well as a parameter array.
|
|
13
|
+
* Warnings when Mustache will render an undefined variable.
|
|
14
|
+
* Fixes the output field option so that nested objects can be used.
|
|
15
|
+
* Fixes the checkbox for the pool shrink option.
|
|
16
|
+
* Uses ace/mode/sql for the SQL input field.
|
|
17
|
+
* Connection nodes can have individually defined names.
|
|
18
|
+
* Selectable SQL syntax checker.
|
|
19
|
+
* Allows parameters to be passed as an object, mapping values to named parameters in the query.
|
|
18
20
|
|
|
19
21
|
## Installation
|
|
20
22
|
|
|
21
|
-
This package is not available from within the Node
|
|
23
|
+
This package is not available from within the Node-RED palette tool. Instead, in your Node-RED user directory (usually `~/.node-red/`), download through the `npm` utility:
|
|
22
24
|
```
|
|
23
25
|
npm install node-red-contrib-odbcmj
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
For the `odbc` connector requirements, please see [the documentation for that package](https://www.npmjs.com/package/odbc#requirements).
|
|
27
29
|
|
|
28
|
-
|
|
29
30
|
## Usage
|
|
30
31
|
|
|
31
32
|
`node-red-contrib-odbcmj` provides two nodes:
|
|
32
33
|
|
|
33
|
-
* **`odbc config`**: A configuration node for defining your connection string and managing your connection parameters
|
|
34
|
-
|
|
35
|
-
* **`odbc`**: A node for running queries with or without parameters passed using Mustache syntax or using a parameter array.
|
|
36
|
-
|
|
34
|
+
* **`odbc config`**: A configuration node for defining your connection string and managing your connection parameters.
|
|
35
|
+
* **`odbc`**: A node for running queries with or without parameters passed using Mustache syntax, a parameter array, or a parameter object.
|
|
37
36
|
|
|
38
37
|
### `odbc config`
|
|
39
38
|
|
|
@@ -43,43 +42,45 @@ A configuration node that manages connections in an `odbc.pool` object. [Can tak
|
|
|
43
42
|
|
|
44
43
|
* (**required**) **`connectionString`**: <`string`>
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
An ODBC connection string that defines your DSN and/or connection string options. Check your ODBC driver documentation for more information about valid connection strings.
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
```
|
|
49
|
+
DSN=MyDSN;DFT=2;
|
|
50
|
+
```
|
|
47
51
|
|
|
48
|
-
Example:
|
|
49
|
-
```
|
|
50
|
-
DSN=MyDSN;DFT=2;
|
|
51
|
-
```
|
|
52
52
|
* (optional) **`initialSize`**: <`number`>
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
The number of connections created in the pool when it is initialized. Default: 5.
|
|
55
55
|
|
|
56
56
|
* (optional) **`incrementSize`**: <`number`>
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
The number of connections that are created when the pool is exhausted. Default: 5.
|
|
59
59
|
|
|
60
60
|
* (optional) **`maxSize`**: <`number`>
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
The maximum number of connections allowed in the pool before it won't create any more. Default: 15.
|
|
63
63
|
|
|
64
64
|
* (optional) **`shrinkPool`**: <`boolean`>
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Whether the number of connections should be reduced to `initialSize` when they are returned to the pool. Default: true.
|
|
67
67
|
|
|
68
68
|
* (optional) **`connectionTimeout`**: <`number`>
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
The number of seconds for a connection to remain idle before closing. Default: 3.
|
|
71
71
|
|
|
72
72
|
* (optional) **`loginTimeout`**: <`number`>
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
The number of seconds for an attempt to create a connection before returning to the application. Default: 3.
|
|
75
75
|
|
|
76
76
|
* (optional) **`syntaxChecker`**: <`boolean`>
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
Whether the syntax validator is activated or not. If activated, the query string will be [parsed](https://www.npmjs.com/package/node-sql-parser#create-ast-for-sql-statement) and appended as an object to the output message with a key named `parsedSql`. Default: false.
|
|
79
79
|
|
|
80
80
|
* (optional) **`syntax`**: <`string`>
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
Dropdown list of the available [SQL flavors available](https://www.npmjs.com/package/node-sql-parser#supported-database-sql-syntax). Default: mysql.
|
|
83
|
+
|
|
83
84
|
|
|
84
85
|
### `odbc`
|
|
85
86
|
|
|
@@ -89,46 +90,48 @@ A node that runs a query when input is received. Each instance of the node can d
|
|
|
89
90
|
|
|
90
91
|
* (**required**) **`connection`**: <`odbc config`>
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
The ODBC pool node that defines the connection settings and manages the connection pool used by this node.
|
|
93
94
|
|
|
94
|
-
* (optional) **`
|
|
95
|
+
* (optional) **`queryType`**: <`string`>
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
Selects the type of query to execute. Options are:
|
|
98
|
+
* `query`: A regular SQL query. Parameters can be passed using Mustache templating or an array in `msg.parameters`.
|
|
99
|
+
* `statement`: A prepared statement. Requires `msg.parameters`.
|
|
97
100
|
|
|
98
|
-
|
|
101
|
+
* (optional) **`query`**: <`string`>
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
A valid SQL query string.
|
|
104
|
+
* For `queryType: "query"`, it can contain parameters inserted using Mustache syntax (e.g., `{{{payload}}}`). You can also use placeholders (`?`) and provide an array of values in `msg.parameters`.
|
|
105
|
+
* For `queryType: "statement"`, it should use placeholders (`?`) for parameters.
|
|
101
106
|
|
|
102
|
-
|
|
107
|
+
* (**required**) **`result to`**: <`dot-notation string`>
|
|
103
108
|
|
|
104
|
-
|
|
109
|
+
The JSON nested element structure that will contain the result output. The string must be a valid JSON object structure using dot-notation, minus the `msg.` (e.g., `payload.results`) and must not start or end with a period. Square bracket notation is not allowed. The node input object is carried out to the output, as long as the output object name does not conflict with it. If the targeted output JSON object was already present in the input, the result from the query will be appended to it if it was itself an object (but not an array); otherwise, the original key/value pair will be overwritten.
|
|
105
110
|
|
|
106
|
-
|
|
111
|
+
Example:
|
|
107
112
|
|
|
108
|
-
|
|
113
|
+
* `input msg: {"payload": {"result": {"othervalue": 10} } };`
|
|
114
|
+
* `result to: payload.results.values`
|
|
109
115
|
|
|
110
|
-
In this case, `values` will be appended to `result`
|
|
116
|
+
In this case, `values` will be appended to `result` without overwriting `othervalue`. If `result` had been a string, then it would have been replaced by `values`.
|
|
111
117
|
|
|
112
118
|
#### Inputs
|
|
113
119
|
|
|
114
|
-
The `odbc` node accepts a message input that
|
|
115
|
-
|
|
116
|
-
- a `payload` that contains a valid JSON string which itself contains a nested `query` key/value pair where the value is the SQL string. Ex: `msg.payload = "{'query':'<sql string>'}"`
|
|
117
|
-
- a `payload` object with a nested `query` key/value pair where the value is the SQL string. Ex: `msg.payload.query = '<sql string>'`
|
|
118
|
-
- a `query` key/value pair where the value is the SQL string. Ex: `msg.query = '<sql string>'`
|
|
120
|
+
The `odbc` node accepts a message input that can contain:
|
|
119
121
|
|
|
120
|
-
*
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
* **`query`**: <`string`> A valid SQL query string. This overrides the query defined in the node properties.
|
|
123
|
+
* **`payload`**:
|
|
124
|
+
* A JSON string containing a `query` property with the SQL string.
|
|
125
|
+
* An object with a `query` property containing the SQL string.
|
|
126
|
+
* **`parameters`**: <`array` or `object`>
|
|
127
|
+
* Required for prepared statements (`queryType: "statement"`).
|
|
128
|
+
* Can be an array of values or an object mapping parameter names to values.
|
|
129
|
+
* For regular queries (`queryType: "query"`) with placeholders (`?`), provide an array of values.
|
|
123
130
|
|
|
124
131
|
#### Outputs
|
|
125
132
|
|
|
126
|
-
Returns a message containing
|
|
127
|
-
|
|
128
|
-
* **`output object`**: <`array`>
|
|
129
|
-
|
|
130
|
-
The [`odbc` result array](https://www.npmjs.com/package/odbc#result-array) returned from the query.
|
|
131
|
-
|
|
132
|
-
* **`odbc`**: <`object`>
|
|
133
|
+
Returns a message containing:
|
|
133
134
|
|
|
134
|
-
|
|
135
|
+
* **`output object`**: <`array`> The `odbc` result array returned from the query.
|
|
136
|
+
* **`odbc`**: <`object`> Contains additional information returned by the `odbc` module.
|
|
137
|
+
* **`parsedQuery`**: <`object`> (Optional) The parsed SQL query if the syntax checker is enabled.
|
package/odbc.html
CHANGED
|
@@ -1,249 +1,290 @@
|
|
|
1
1
|
<script type="text/javascript">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
2
|
+
RED.nodes.registerType('odbc config',{
|
|
3
|
+
category: 'config',
|
|
4
|
+
defaults: {
|
|
5
|
+
connectionString: {value:"", required:true},
|
|
6
|
+
name: {value:""},
|
|
7
|
+
initialSize: {value:5},
|
|
8
|
+
incrementSize: {value:5},
|
|
9
|
+
maxSize: {value:15},
|
|
10
|
+
shrink:{value:true},
|
|
11
|
+
syntaxtick:{value:false},
|
|
12
|
+
syntax: {value:"mysql"},
|
|
13
|
+
connectionTimeout:{value:3},
|
|
14
|
+
loginTimeout:{value:3}
|
|
15
|
+
},
|
|
16
|
+
label: function() {
|
|
17
|
+
return this.name || 'odbc config';
|
|
18
|
+
},
|
|
19
|
+
oneditprepare: function() {
|
|
20
|
+
$(".input-syntax").hide();
|
|
21
|
+
$("#node-config-input-syntaxtick").change(function() {
|
|
22
|
+
if (this.checked){
|
|
23
|
+
$(".input-syntax").show();
|
|
24
|
+
} else {
|
|
25
|
+
$(".input-syntax").hide();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
});
|
|
29
|
+
});
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
32
|
<script type="text/html" data-template-name="odbc config">
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
34
|
+
<div class="form-row">
|
|
35
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
36
|
+
<input type="text" id="node-config-input-name">
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="form-row">
|
|
40
|
+
<label for="node-config-input-connectionString"><i class="icon-bookmark"></i>Connection String</label>
|
|
41
|
+
<input type="text" id="node-config-input-connectionString" placeholder="DSN=...;">
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="form-row">
|
|
45
|
+
<label for="node-config-input-initialSize"><i class="icon-bookmark"></i>Initial Size</label>
|
|
46
|
+
<input type="number" id="node-config-input-initialSize" placeholder="5">
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="form-row">
|
|
50
|
+
<label for="node-config-input-incrementSize"><i class="icon-bookmark"></i>Increment Size</label>
|
|
51
|
+
<input type="number" id="node-config-input-incrementSize" placeholder="5">
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="form-row">
|
|
55
|
+
<label for="node-config-input-maxSize"><i class="icon-bookmark"></i>Max Size</label>
|
|
56
|
+
<input type="number" id="node-config-input-maxSize" placeholder="15">
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="form-row">
|
|
60
|
+
<label for="node-config-input-shrink"><i class="icon-bookmark"></i>Shrink Pool</label>
|
|
61
|
+
<input type="checkbox" id="node-config-input-shrink" style="margin-left:0px; vertical-align:top; width:auto !important;">
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="form-row">
|
|
65
|
+
<label for="node-config-input-connectionTimeout"><i class="icon-bookmark"></i>Connection Timeout (sec)</label>
|
|
66
|
+
<input type="number" id="node-config-input-connectionTimeout" placeholder="3">
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="form-row">
|
|
70
|
+
<label for="node-config-input-loginTimeout"><i class="icon-bookmark"></i>Login Timeout (sec)</label>
|
|
71
|
+
<input type="number" id="node-config-input-loginTimeout" placeholder="3">
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="form-row">
|
|
75
|
+
<label for="node-config-input-syntaxtick" style="width: auto;"><i class="icon-bookmark"></i>Syntax Checker</label>
|
|
76
|
+
<input type="checkbox" id="node-config-input-syntaxtick" style="display: inline-block; width: auto; vertical-align: top;">
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="form-row input-syntax">
|
|
80
|
+
<label for=""><i class="icon-bookmark"></i> Syntax</label>
|
|
81
|
+
<select id="node-config-input-syntax" style="width: 70%">
|
|
82
|
+
<option value="bigquery">BigQuery</option>
|
|
83
|
+
<option value="db2">DB2</option>
|
|
84
|
+
<option value="hive">Hive</option>
|
|
85
|
+
<option value="mariadb">MariaDB</option>
|
|
86
|
+
<option value="mysql">Mysql</option>
|
|
87
|
+
<option value="postgresql">PostgresQL</option>
|
|
88
|
+
<option value="sqlite">Sqlite</option>
|
|
89
|
+
<option value="transactsql">TransactSQL</option>
|
|
90
|
+
<option value="flinksql">FlinkSQL</option>
|
|
91
|
+
</select>
|
|
92
|
+
</div>
|
|
93
93
|
</script>
|
|
94
94
|
|
|
95
95
|
<script type="text/javascript">
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
96
|
+
RED.nodes.registerType('odbc',{
|
|
97
|
+
category: 'storage',
|
|
98
|
+
color: '#89A5C0',
|
|
99
|
+
defaults: {
|
|
100
|
+
name: {value:""},
|
|
101
|
+
connection: {type:"odbc config", required:true},
|
|
102
|
+
queryType: { value: "query" },
|
|
103
|
+
query: {value: ""},
|
|
104
|
+
parameters: {value: ""},
|
|
105
|
+
outputObj: {value:"payload"}
|
|
106
|
+
},
|
|
107
|
+
inputs:1,
|
|
108
|
+
outputs:1,
|
|
109
|
+
icon: "db.svg",
|
|
110
|
+
label: function() {
|
|
111
|
+
return this.name||"odbc";
|
|
112
|
+
},
|
|
113
|
+
oneditprepare: function() {
|
|
114
|
+
this.editor = RED.editor.createEditor({
|
|
115
|
+
id: 'node-input-query-editor',
|
|
116
|
+
mode: 'ace/mode/sql',
|
|
117
|
+
value: this.query
|
|
118
|
+
});
|
|
119
|
+
$("#node-input-queryType").on("change", function() {
|
|
120
|
+
if ($(this).val() === "statement") {
|
|
121
|
+
$("#node-input-parameters").show();
|
|
122
|
+
} else {
|
|
123
|
+
$("#node-input-parameters").hide();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Trigger the change event initially to set the visibility
|
|
128
|
+
$("#node-input-queryType").trigger("change");
|
|
129
|
+
},
|
|
130
|
+
oneditsave: function() {
|
|
131
|
+
this.query = this.editor.getValue();
|
|
132
|
+
this.editor.destroy();
|
|
133
|
+
delete this.editor;
|
|
134
|
+
},
|
|
135
|
+
oneditcancel: function() {
|
|
136
|
+
this.editor.destroy();
|
|
137
|
+
delete this.editor;
|
|
138
|
+
}
|
|
128
139
|
});
|
|
129
140
|
</script>
|
|
130
141
|
|
|
131
142
|
<script type="text/html" data-template-name="odbc">
|
|
132
143
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
<div class="form-row">
|
|
145
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
146
|
+
<input type="text" id="node-input-name">
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div class="form-row">
|
|
150
|
+
<label for="node-input-connection"><i class="fa fa-cog"></i> Connection</label>
|
|
151
|
+
<input type="text" id="node-input-connection">
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div class="form-row">
|
|
155
|
+
<label for="node-input-queryType"><i class="fa fa-code"></i> Query Type</label>
|
|
156
|
+
<select id="node-input-queryType">
|
|
157
|
+
<option value="query">Query</option>
|
|
158
|
+
<option value="statement">Prepared Statement</option>
|
|
159
|
+
</select>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="form-row node-text-editor-row">
|
|
163
|
+
<label for="node-input-query" style="width: 100% !important;"><i class="fa fa-search"></i> Query</label>
|
|
164
|
+
<div style="height: 250px;" class="node-text-editor" id="node-input-query-editor" ></div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div class="form-row" id="node-input-parameters">
|
|
168
|
+
<label for="node-input-parameters-editor"><i class="fa fa-list"></i> Parameters</label>
|
|
169
|
+
<input type="text" id="node-input-parameters-editor">
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div class="form-row">
|
|
173
|
+
<label for="node-input-outputObj"><i class="fa fa-edit"></i> Result to</label>
|
|
174
|
+
<span>msg.</span><input type="text" id="node-input-outputObj" placeholder="payload" style="width: 64%;">
|
|
175
|
+
</div>
|
|
151
176
|
</script>
|
|
152
|
-
|
|
153
177
|
<script type="text/markdown" data-help-name="odbc config">
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
* (**required**) **`connectionString`**: <`string`>
|
|
159
|
-
|
|
160
|
-
An ODBC connection string that defines your DSN and/or connection string options. Check your ODBC driver documentation for more information about valid connection strings.
|
|
161
|
-
|
|
162
|
-
Example:
|
|
163
|
-
```
|
|
164
|
-
DSN=MyDSN;DFT=2;
|
|
165
|
-
```
|
|
166
|
-
* (optional) **`initialSize`**: <`number`>
|
|
167
|
-
|
|
168
|
-
The number of connections created in the Pool when it is initialized. Default: 5.
|
|
169
|
-
|
|
170
|
-
* (optional) **`incrementSize`**: <`number`>
|
|
171
|
-
|
|
172
|
-
The number of connections that are created when the pool is exhausted. Default: 5.
|
|
173
|
-
|
|
174
|
-
* (optional) **`maxSize`**: <`number`>
|
|
175
|
-
|
|
176
|
-
The maximum number of connections allowed in the pool before it won't create any more. Default: 15.
|
|
177
|
-
|
|
178
|
-
* (optional) **`shrinkPool`**: <`boolean`>
|
|
179
|
-
|
|
180
|
-
Whether the number of connections should be reduced to `initialSize` when they are returned to the pool. Default: true.
|
|
181
|
-
|
|
182
|
-
* (optional) **`connectionTimeout`**: <`number`>
|
|
183
|
-
|
|
184
|
-
The number of seconds for a connection to remain idle before closing. Default: 3.
|
|
185
|
-
|
|
186
|
-
* (optional) **`loginTimeout`**: <`number`>
|
|
187
|
-
|
|
188
|
-
The number of seconds for an attempt to create a connection before returning to the application. Default: 3.
|
|
189
|
-
|
|
190
|
-
* (optional) **`syntaxChecker`**: <`boolean`>
|
|
191
|
-
|
|
192
|
-
Whether the syntax validator is activeted or not. If activated, the query string will be [parsed](https://www.npmjs.com/package/node-sql-parser#create-ast-for-sql-statement) and appended as an object to the output message with a key named `parsedQuery`. Default: false.
|
|
193
|
-
|
|
194
|
-
* (optional) **`syntax`**: <`string`>
|
|
195
|
-
|
|
196
|
-
Dropdown list of the available [SQL flavors available](https://www.npmjs.com/package/node-sql-parser#supported-database-sql-syntax). Default: mysql.
|
|
197
|
-
</script>
|
|
178
|
+
A configuration node that manages connections in an `odbc.pool` object.
|
|
179
|
+
[Can take any configuration property recognized by `odbc.pool()`](https://www.npmjs.com/package/odbc/v/2.4.8#constructor-odbcconnectconnectionstring).
|
|
180
|
+
The connection pool will initialize the first time an `odbc` node receives an input message.
|
|
198
181
|
|
|
199
|
-
|
|
200
|
-
A node that runs a query when input is received. Each instance of the node can define its own query string, as well as take a query and/or parameters as input. A query sent as an input message will override any query defined in the node properties.
|
|
182
|
+
## Properties
|
|
201
183
|
|
|
202
|
-
|
|
184
|
+
* (**required**) **`connectionString`**: <`string`>
|
|
203
185
|
|
|
204
|
-
|
|
186
|
+
An ODBC connection string that defines your DSN and/or connection string options.
|
|
187
|
+
Check your ODBC driver documentation for more information about valid connection strings.
|
|
205
188
|
|
|
206
|
-
|
|
189
|
+
Example:
|
|
190
|
+
```
|
|
191
|
+
DSN=MyDSN;DFT=2;
|
|
192
|
+
```
|
|
207
193
|
|
|
208
|
-
* (optional) **`
|
|
194
|
+
* (optional) **`initialSize`**: <`number`>
|
|
209
195
|
|
|
210
|
-
|
|
196
|
+
The number of connections created in the pool when it is initialized. Default: 5.
|
|
211
197
|
|
|
212
|
-
|
|
198
|
+
* (optional) **`incrementSize`**: <`number`>
|
|
213
199
|
|
|
214
|
-
|
|
200
|
+
The number of connections that are created when the pool is exhausted. Default: 5.
|
|
215
201
|
|
|
216
|
-
|
|
202
|
+
* (optional) **`maxSize`**: <`number`>
|
|
217
203
|
|
|
218
|
-
|
|
204
|
+
The maximum number of connections allowed in the pool before it won't create any more. Default: 15.
|
|
219
205
|
|
|
220
|
-
|
|
206
|
+
* (optional) **`shrinkPool`**: <`boolean`>
|
|
221
207
|
|
|
222
|
-
|
|
208
|
+
Whether the number of connections should be reduced to `initialSize` when they are returned to the pool. Default: true.
|
|
223
209
|
|
|
224
|
-
|
|
210
|
+
* (optional) **`connectionTimeout`**: <`number`>
|
|
225
211
|
|
|
226
|
-
|
|
212
|
+
The number of seconds for a connection to remain idle before closing. Default: 3.
|
|
227
213
|
|
|
228
|
-
|
|
214
|
+
* (optional) **`loginTimeout`**: <`number`>
|
|
229
215
|
|
|
230
|
-
|
|
231
|
-
- a `payload` object with a nested `query` key/value pair where the value is the SQL string. Ex: `msg.payload.query = '<sql string>'`
|
|
232
|
-
- a `query` key/value pair where the value is the SQL string. Ex: `msg.query = '<sql string>'`
|
|
216
|
+
The number of seconds for an attempt to create a connection before returning to the application. Default: 3.
|
|
233
217
|
|
|
234
|
-
* (optional) **`
|
|
218
|
+
* (optional) **`syntaxChecker`**: <`boolean`>
|
|
235
219
|
|
|
236
|
-
|
|
220
|
+
Whether the syntax validator is activated or not. If activated, the query string will be
|
|
221
|
+
[parsed](https://www.npmjs.com/package/node-sql-parser#create-ast-for-sql-statement)
|
|
222
|
+
and appended as an object to the output message with a key named `parsedQuery`. Default: false.
|
|
237
223
|
|
|
238
|
-
|
|
224
|
+
* (optional) **`syntax`**: <`string`>
|
|
239
225
|
|
|
240
|
-
|
|
226
|
+
Dropdown list of the available [SQL flavors available](https://www.npmjs.com/package/node-sql-parser#supported-database-sql-syntax).
|
|
227
|
+
Default: mysql.
|
|
228
|
+
</script>
|
|
241
229
|
|
|
242
|
-
|
|
230
|
+
<script type="text/markdown" data-help-name="odbc">
|
|
231
|
+
A node that runs a query when input is received. Each instance of the node can define its own query string,
|
|
232
|
+
as well as take a query and/or parameters as input. A query sent as an input message will override any query
|
|
233
|
+
defined in the node properties.
|
|
243
234
|
|
|
244
|
-
|
|
235
|
+
## Properties
|
|
245
236
|
|
|
246
|
-
* **`
|
|
237
|
+
* (**required**) **`connection`**: <`odbc config`>
|
|
247
238
|
|
|
248
|
-
|
|
249
|
-
|
|
239
|
+
The ODBC pool node that defines the connection settings and manages the connection pool used by this node.
|
|
240
|
+
|
|
241
|
+
* (optional) **`queryType`**: <`string`>
|
|
242
|
+
|
|
243
|
+
Selects the type of query to execute. Options are:
|
|
244
|
+
* `query`: A regular SQL query. Parameters can be passed using Mustache templating, a parameter array in `msg.parameters`, or directly in the query string.
|
|
245
|
+
* `statement`: A prepared statement (requires `msg.parameters`).
|
|
246
|
+
|
|
247
|
+
* (optional) **`query`**: <`string`>
|
|
248
|
+
|
|
249
|
+
A valid SQL query string.
|
|
250
|
+
* For `queryType: "query"`, it can contain parameters inserted using Mustache syntax (e.g., `{{{payload}}}`). You can also use placeholders (`?`) and provide an array of values in `msg.parameters`, or embed the parameters directly in the query string.
|
|
251
|
+
* For `queryType: "statement"`, it should use placeholders (`?`) for parameters.
|
|
252
|
+
|
|
253
|
+
* (**required**) **`result to`**: <`dot-notation string`>
|
|
254
|
+
|
|
255
|
+
The JSON nested element structure that will contain the result output. The string must be a valid
|
|
256
|
+
JSON object structure using dot-notation, minus the `msg.` (e.g., `payload.results`) and must not
|
|
257
|
+
start or end with a period. Square bracket notation is not allowed. The node input object is carried
|
|
258
|
+
out to the output, as long as the output object name does not conflict with it. If the targeted output
|
|
259
|
+
JSON object was already present in the input, the result from the query will be appended to it if it
|
|
260
|
+
was itself an object (but not an array); otherwise, the original key/value pair will be overwritten.
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
|
|
264
|
+
* `input msg: {"payload": {"result": {"othervalue": 10} } };`
|
|
265
|
+
* `result to: payload.results.values`
|
|
266
|
+
|
|
267
|
+
In this case, `values` will be appended to `result` without overwriting `othervalue`.
|
|
268
|
+
If `result` had been a string, then it would have been replaced by `values`.
|
|
269
|
+
|
|
270
|
+
## Inputs
|
|
271
|
+
|
|
272
|
+
The `odbc` node accepts a message input that can contain:
|
|
273
|
+
|
|
274
|
+
* **`query`**: <`string`> A valid SQL query string. This overrides the query defined in the node properties.
|
|
275
|
+
* **`payload`**:
|
|
276
|
+
* A JSON string containing a `query` property with the SQL string.
|
|
277
|
+
* An object with a `query` property containing the SQL string.
|
|
278
|
+
* **`parameters`**: <`array` or `object`>
|
|
279
|
+
* Required for prepared statements (`queryType: "statement"`).
|
|
280
|
+
* Can be an array of values or an object mapping parameter names to values.
|
|
281
|
+
* For regular queries (`queryType: "query"`) with placeholders (`?`), provide an array of values.
|
|
282
|
+
|
|
283
|
+
## Outputs
|
|
284
|
+
|
|
285
|
+
Returns a message containing:
|
|
286
|
+
|
|
287
|
+
* **`output object`**: <`array`> The `odbc` result array returned from the query.
|
|
288
|
+
* **`odbc`**: <`object`> Contains additional information returned by the `odbc` module.
|
|
289
|
+
* **`parsedQuery`**: <`object`> (Optional) The parsed SQL query if the syntax checker is enabled.
|
|
290
|
+
</script>
|
package/odbc.js
CHANGED
|
@@ -1,223 +1,270 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
const odbcModule = require('odbc');
|
|
3
|
+
const mustache = require('mustache');
|
|
4
|
+
const objPath = require('object-path');
|
|
5
|
+
|
|
6
|
+
// --- ODBC Configuration Node ---
|
|
7
|
+
function poolConfig(config) {
|
|
8
|
+
RED.nodes.createNode(this, config);
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.pool = null;
|
|
11
|
+
this.connecting = false;
|
|
12
|
+
|
|
13
|
+
const enableSyntaxChecker = this.config.syntaxtick; // Renamed for clarity
|
|
14
|
+
const syntax = this.config.syntax;
|
|
15
|
+
delete this.config.syntaxtick;
|
|
16
|
+
delete this.config.syntax;
|
|
17
|
+
|
|
18
|
+
// Create a SQL parser if syntax check is enabled
|
|
19
|
+
this.parser = enableSyntaxChecker
|
|
20
|
+
? new require('node-sql-parser/build/' + syntax).Parser()
|
|
21
|
+
: null;
|
|
22
|
+
|
|
23
|
+
// Convert numeric config params to integers
|
|
24
|
+
for (const [key, value] of Object.entries(this.config)) {
|
|
25
|
+
if (!isNaN(parseInt(value))) {
|
|
26
|
+
this.config[key] = parseInt(value);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Connect to the database and create a connection pool
|
|
31
|
+
this.connect = async () => {
|
|
32
|
+
if (!this.pool) {
|
|
33
|
+
try {
|
|
34
|
+
this.pool = await odbcModule.pool(this.config);
|
|
35
|
+
this.connecting = false;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Handle connection errors (e.g., log the error, set node status)
|
|
38
|
+
this.error(`Error creating connection pool: ${error}`);
|
|
39
|
+
this.status({ fill: "red", shape: "ring", text: "Connection error" });
|
|
40
|
+
throw error; // Re-throw to prevent further execution
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return await this.pool.connect();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Close the connection pool when the node is closed
|
|
47
|
+
this.on('close', async (removed, done) => {
|
|
48
|
+
if (removed && this.pool) {
|
|
49
|
+
try {
|
|
50
|
+
await this.pool.close();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
// Handle errors during pool closure
|
|
53
|
+
this.error(`Error closing connection pool: ${error}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
done();
|
|
57
|
+
});
|
|
25
58
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
59
|
+
|
|
60
|
+
RED.nodes.registerType('odbc config', poolConfig);
|
|
61
|
+
|
|
62
|
+
// --- ODBC Query Node ---
|
|
63
|
+
function odbc(config) {
|
|
64
|
+
RED.nodes.createNode(this, config);
|
|
65
|
+
this.config = config;
|
|
66
|
+
this.poolNode = RED.nodes.getNode(this.config.connection);
|
|
67
|
+
this.name = this.config.name;
|
|
68
|
+
|
|
69
|
+
this.runQuery = async function(msg, send, done) {
|
|
70
|
+
try {
|
|
71
|
+
this.status({ fill: "blue", shape: "dot", text: "querying..." });
|
|
72
|
+
this.config.outputObj = msg?.output || this.config?.outputObj;
|
|
73
|
+
|
|
74
|
+
const isPreparedStatement = this.config.queryType === "statement";
|
|
75
|
+
this.queryString = this.config.query;
|
|
76
|
+
|
|
77
|
+
// --- Construct the query string ---
|
|
78
|
+
if (!isPreparedStatement && this.queryString) {
|
|
79
|
+
// Handle Mustache templating for regular queries
|
|
80
|
+
for (const parsed of mustache.parse(this.queryString)) {
|
|
81
|
+
if (parsed[0] === "name" || parsed[0] === "&") {
|
|
82
|
+
if (!objPath.has(msg, parsed[1])) {
|
|
83
|
+
this.warn(`Mustache parameter "${parsed[1]}" is absent and will render to undefined`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
this.queryString = mustache.render(this.queryString, msg);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Handle cases where the query is provided in the message
|
|
91
|
+
if (msg?.query) {
|
|
92
|
+
this.queryString = msg.query;
|
|
93
|
+
} else if (msg?.payload) {
|
|
94
|
+
if (typeof msg.payload === 'string') {
|
|
95
|
+
try {
|
|
96
|
+
const payloadJson = JSON.parse(msg.payload);
|
|
97
|
+
if (payloadJson?.query && typeof payloadJson.query === 'string') {
|
|
98
|
+
this.queryString = payloadJson.query;
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {} // Ignore JSON parsing errors
|
|
101
|
+
} else if (msg.payload?.query && typeof msg.payload.query === 'string') {
|
|
102
|
+
this.queryString = msg.payload.query;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!this.queryString) {
|
|
107
|
+
throw new Error("No query to execute");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- Parameter validation for prepared statements ---
|
|
111
|
+
if (isPreparedStatement) {
|
|
112
|
+
if (this.queryString.includes('?')) {
|
|
113
|
+
if (!msg?.parameters) {
|
|
114
|
+
throw new Error("Prepared statement requires msg.parameters");
|
|
115
|
+
} else if (!Array.isArray(msg.parameters)) {
|
|
116
|
+
throw new Error("msg.parameters must be an array");
|
|
117
|
+
} else if ((this.queryString.match(/\?/g) || []).length !== msg.parameters.length) {
|
|
118
|
+
throw new Error("Incorrect number of parameters");
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
this.warn("Prepared statement without parameters. Consider using a regular query.");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// --- Syntax check ---
|
|
126
|
+
if (this.poolNode?.parser) {
|
|
127
|
+
try {
|
|
128
|
+
this.parseSql = this.poolNode.parser.astify(structuredClone(this.queryString));
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new Error("SQL syntax error");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- Output object validation ---
|
|
135
|
+
if (!this.config.outputObj) {
|
|
136
|
+
throw new Error("Invalid output object definition");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const reg = new RegExp('^((?![,;:`\\[\\]{}+=()!"$%?&*|<>\\/^¨`\\s]).)*$');
|
|
140
|
+
if (!this.config.outputObj.match(reg) ||
|
|
141
|
+
this.config.outputObj.charAt(0) === "." ||
|
|
142
|
+
this.config.outputObj.charAt(this.config.outputObj.length - 1) === ".") {
|
|
143
|
+
throw new Error("Invalid output field");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// --- Get a connection from the pool ---
|
|
147
|
+
try {
|
|
148
|
+
this.connection = await this.poolNode.connect();
|
|
149
|
+
if (!this.connection) {
|
|
150
|
+
throw new Error("No connection available");
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
// Handle connection errors (e.g., log the error, set node status)
|
|
154
|
+
this.error(`Error getting connection: ${error}`);
|
|
155
|
+
this.status({ fill: "red", shape: "ring", text: "Connection error" });
|
|
156
|
+
throw error; // Re-throw to prevent further execution
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
let result;
|
|
161
|
+
if (isPreparedStatement) {
|
|
162
|
+
const stmt = await this.connection.prepare(this.queryString);
|
|
163
|
+
let values = msg.parameters;
|
|
164
|
+
|
|
165
|
+
if (typeof values === 'object' && !Array.isArray(values)) {
|
|
166
|
+
// Assuming your parameters are like this: (param1, param2, param3)
|
|
167
|
+
const paramNames = this.queryString.match(/\(([^)]*)\)/)[1].split(',').map(el => el.trim());
|
|
168
|
+
|
|
169
|
+
// Create an ordered array of values
|
|
170
|
+
values = paramNames.map(name => msg.parameters[name]);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Execute the prepared statement
|
|
174
|
+
const result = await stmt.execute(values);
|
|
175
|
+
stmt.finalize();
|
|
176
|
+
} else {
|
|
177
|
+
// --- Execute regular query ---
|
|
178
|
+
result = await this.connection.query(this.queryString, msg?.parameters);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (result) {
|
|
182
|
+
// --- Process and send the result ---
|
|
183
|
+
const otherParams = {};
|
|
184
|
+
for (const [key, value] of Object.entries(result)) {
|
|
185
|
+
if (isNaN(parseInt(key))) {
|
|
186
|
+
otherParams[key] = value;
|
|
187
|
+
delete result[key];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
objPath.set(msg, this.config.outputObj, result);
|
|
191
|
+
if (this.parseSql) {
|
|
192
|
+
msg.parsedQuery = this.parseSql;
|
|
193
|
+
}
|
|
194
|
+
if (Object.keys(otherParams).length) {
|
|
195
|
+
msg.odbc = otherParams;
|
|
196
|
+
}
|
|
197
|
+
this.status({ fill: 'green', shape: 'dot', text: 'success' });
|
|
198
|
+
send(msg);
|
|
199
|
+
} else {
|
|
200
|
+
throw new Error("The query returned no results");
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// Handle query errors (e.g., log the error, set node status)
|
|
204
|
+
this.error(`Error executing query: ${error}`);
|
|
205
|
+
this.status({ fill: "red", shape: "ring", text: "Query error" });
|
|
206
|
+
throw error; // Re-throw to trigger the outer catch block
|
|
207
|
+
} finally {
|
|
208
|
+
await this.connection.close();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (done) {
|
|
212
|
+
done();
|
|
213
|
+
}
|
|
214
|
+
} catch (err) {
|
|
215
|
+
this.status({ fill: "red", shape: "ring", text: err.message || "query error" });
|
|
216
|
+
if (done) {
|
|
217
|
+
done(err);
|
|
85
218
|
} else {
|
|
86
|
-
|
|
87
|
-
this.queryString = payloadJson.query || this.queryString;
|
|
219
|
+
this.error(err, msg);
|
|
88
220
|
}
|
|
89
|
-
}
|
|
90
221
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// --- Check connection pool before running query ---
|
|
225
|
+
this.checkPool = async function(msg, send, done) {
|
|
226
|
+
try {
|
|
227
|
+
if (this.poolNode.connecting) {
|
|
228
|
+
this.warn("Waiting for connection pool...");
|
|
229
|
+
this.status({ fill: "yellow", shape: "ring", text: "requesting pool" });
|
|
230
|
+
setTimeout(() => {
|
|
231
|
+
this.checkPool(msg, send, done);
|
|
232
|
+
}, 1000);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!this.poolNode.pool) {
|
|
237
|
+
this.poolNode.connecting = true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
await this.runQuery(msg, send, done);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
this.status({ fill: "red", shape: "dot", text: "operation failed" });
|
|
243
|
+
if (done) {
|
|
244
|
+
done(err);
|
|
245
|
+
} else {
|
|
246
|
+
this.error(err, msg);
|
|
247
|
+
}
|
|
97
248
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
else if(this.queryString.includes('\?') && !Array.isArray(msg.parameters)){
|
|
112
|
-
this.status({fill: "red", shape: "ring",text: "Invalid statement"});
|
|
113
|
-
throw new Error("the query string includes question marks but msg.params is not an array");
|
|
114
|
-
}
|
|
115
|
-
if(this.queryString.includes('\?')){
|
|
116
|
-
if((this.queryString.match(/\?/g) || []).length != msg.parameters.length){
|
|
117
|
-
this.status({fill: "red", shape: "ring",text: "Invalid statement"});
|
|
118
|
-
throw new Error("the number of parameters provided doesn't match the number of ? markers");
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
//----Check if the query string is a syntaxically valid SQL query
|
|
122
|
-
if(this.poolNode?.parser){
|
|
123
|
-
let clone = structuredClone(this.queryString);
|
|
124
|
-
try { this.parseSql = this.poolNode.parser.astify(clone) }
|
|
125
|
-
catch (error) {
|
|
126
|
-
this.status({fill: "red", shape: "ring",text: "Invalid query"});
|
|
127
|
-
throw new Error("the query failed the sql syntax check");
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
//---If no output object was specified.
|
|
131
|
-
if (!this.config.outputObj){
|
|
132
|
-
this.status({fill: "red", shape: "ring",text: "Invalid output field"});
|
|
133
|
-
throw new Error("invalid output object definition");
|
|
134
|
-
}
|
|
135
|
-
//----If the output string contains illegal punctuation.
|
|
136
|
-
const reg = new RegExp('^((?![,;:`\[\]{}+=()!"$%?&*|<>\/^¨`\s]).)*$')
|
|
137
|
-
if (!this.config.outputObj.match(reg)){
|
|
138
|
-
this.status({fill: "red", shape: "ring",text: "Invalid output field"});
|
|
139
|
-
throw new Error("invalid output field");
|
|
140
|
-
}
|
|
141
|
-
//----If ouput string starts or ends with a period (usually because the user left one there inadvertently)
|
|
142
|
-
if (this.config.outputObj.charAt(0) == "." || this.config.outputObj.charAt(this.config.outputObj.length-1) == "."){
|
|
143
|
-
this.status({fill: "red", shape: "ring",text: "Invalid output field"});
|
|
144
|
-
throw new Error("invalid output field");
|
|
145
|
-
}
|
|
146
|
-
//---Retrieving one connection from the pool.
|
|
147
|
-
this.connection = null;
|
|
148
|
-
try { this.connection = await this.poolNode.connect() }
|
|
149
|
-
catch (error) {
|
|
150
|
-
this.status({fill: "red", shape: "ring",text: "connection error"});
|
|
151
|
-
throw new Error(error);
|
|
152
|
-
}
|
|
153
|
-
if(!this.connection){
|
|
154
|
-
this.status({fill: "red", shape: "ring",text: "no connection"});
|
|
155
|
-
throw new Error("no connection");
|
|
156
|
-
}
|
|
157
|
-
//----Actual attempt to send the query to the ODBC driver.
|
|
158
|
-
try {
|
|
159
|
-
const result = await this.connection.query(this.queryString, msg?.parameters);
|
|
160
|
-
if(result){
|
|
161
|
-
//----Sending the results to the defined dot notation object.
|
|
162
|
-
let otherParams = {};
|
|
163
|
-
for(const [key, value] of Object.entries(result)){
|
|
164
|
-
if(isNaN(parseInt(key))){
|
|
165
|
-
otherParams[key] = value;
|
|
166
|
-
delete result[key];
|
|
167
|
-
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
this.on('input', this.checkPool);
|
|
252
|
+
|
|
253
|
+
// --- Close the connection when the node is closed ---
|
|
254
|
+
this.on('close', async (done) => {
|
|
255
|
+
if (this.connection) {
|
|
256
|
+
try {
|
|
257
|
+
await this.connection.close();
|
|
258
|
+
} catch (error) {
|
|
259
|
+
// Handle connection close errors
|
|
260
|
+
this.error(`Error closing connection: ${error}`);
|
|
261
|
+
}
|
|
168
262
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
} catch (error) {
|
|
179
|
-
this.status({fill: "red", shape: "ring",text: "query error"});
|
|
180
|
-
throw new Error(error);
|
|
181
|
-
}
|
|
182
|
-
finally {
|
|
183
|
-
//----Close the connection.
|
|
184
|
-
await this.connection.close();
|
|
185
|
-
}
|
|
186
|
-
if (done) done();
|
|
187
|
-
} catch (err) {
|
|
188
|
-
if (done) {done(err)} else {this.error(err, msg)}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
};
|
|
192
|
-
//----This function runs following an input message.
|
|
193
|
-
this.checkPool = async function(msg, send, done) {
|
|
194
|
-
//----Loops while other nodes are awaiting connections. See poolConfig.
|
|
195
|
-
try {
|
|
196
|
-
if (this.poolNode.connecting) {
|
|
197
|
-
this.warn("received msg while requesting pool");
|
|
198
|
-
this.status({fill: "yellow", shape: "ring",text: "requesting pool"});
|
|
199
|
-
setTimeout(() => {
|
|
200
|
-
this.checkPool(msg, send, done);
|
|
201
|
-
}, 1000);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
//----On initialization, the pool will be null. Set connecting to true so that other nodes are immediately blocked, then call runQuery (which will actually do the pool initialization)
|
|
205
|
-
if (this.poolNode.pool == null) this.poolNode.connecting = true;
|
|
206
|
-
//----If a connection is available, run the runQuery function.
|
|
207
|
-
await this.runQuery(msg, send, done);
|
|
208
|
-
} catch (err) {
|
|
209
|
-
this.status({fill: "red",shape: "dot", text: "operation failed"});
|
|
210
|
-
if (done) {done(err)} else {this.error(err, msg)}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
};
|
|
214
|
-
this.on('input', this.checkPool);
|
|
215
|
-
this.on('close', async (done) => {
|
|
216
|
-
if(this?.connection) await this.connection.close();
|
|
217
|
-
done();
|
|
218
|
-
})
|
|
219
|
-
this.status({fill:'green',shape:'dot',text:'ready'});
|
|
220
|
-
}
|
|
221
|
-
//----Registering the odbc node to the odbc function.
|
|
222
|
-
RED.nodes.registerType("odbc", odbc);
|
|
223
|
-
};
|
|
263
|
+
done();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
this.status({ fill: 'green', shape: 'dot', text: 'ready' });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
RED.nodes.registerType("odbc", odbc);
|
|
270
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bkmj/node-red-contrib-odbcmj",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Node Red implementation of odbc.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
],
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"mustache": "^4.2.0",
|
|
13
|
-
"node-sql-parser": "^
|
|
13
|
+
"node-sql-parser": "^5.3.5",
|
|
14
14
|
"object-path": "^0.11.8",
|
|
15
|
-
"odbc": "^2.4.
|
|
15
|
+
"odbc": "^2.4.9"
|
|
16
16
|
},
|
|
17
17
|
"node-red": {
|
|
18
18
|
"nodes": {
|