@bkmj/node-red-contrib-odbcmj 1.2.4
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 +134 -0
- package/odbc.html +249 -0
- package/odbc.js +223 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# node-red-contrib-odbcmj
|
|
2
|
+
|
|
3
|
+
A Node Red implementation of odbc.js (https://www.npmjs.com/package/odbc). This Node allows to make queries to a database through an ODBC connection. Additionally, parameters can be passed to the SQL query using Mustache syntax or prepared statements.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
## Acknowledgement
|
|
7
|
+
|
|
8
|
+
This node is an unofficial fork of node-red-contrib-odbc from Mark Irish (https://github.com/markdirish/node-red-contrib-odbc) and vastly inspires from it. It also takes ideas from node-red-contrib-odbc2 from AIS Automation (https://github.com/AISAutomation/node-red-contrib-odbc2). Overall changes:
|
|
9
|
+
|
|
10
|
+
- Can use mustache as well as a parameter array.
|
|
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
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
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
|
+
```
|
|
23
|
+
npm install node-red-contrib-odbcmj
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For the `odbc` connector requirements, please see [the documentation for that package](https://www.npmjs.com/package/odbc#requirements).
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
`node-red-contrib-odbcmj` provides two nodes:
|
|
32
|
+
|
|
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
|
+
|
|
37
|
+
|
|
38
|
+
### `odbc config`
|
|
39
|
+
|
|
40
|
+
A configuration node that manages connections in an `odbc.pool` object. [Can take any configuration property recognized by `odbc.pool()`](https://www.npmjs.com/package/odbc#constructor-odbcpoolconnectionstring). The connection pool will initialize the first time an `odbc` node receives an input message.
|
|
41
|
+
|
|
42
|
+
#### Properties
|
|
43
|
+
|
|
44
|
+
* (**required**) **`connectionString`**: <`string`>
|
|
45
|
+
|
|
46
|
+
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.
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
```
|
|
50
|
+
DSN=MyDSN;DFT=2;
|
|
51
|
+
```
|
|
52
|
+
* (optional) **`initialSize`**: <`number`>
|
|
53
|
+
|
|
54
|
+
The number of connections created in the Pool when it is initialized. Default: 5.
|
|
55
|
+
|
|
56
|
+
* (optional) **`incrementSize`**: <`number`>
|
|
57
|
+
|
|
58
|
+
The number of connections that are created when the pool is exhausted. Default: 5.
|
|
59
|
+
|
|
60
|
+
* (optional) **`maxSize`**: <`number`>
|
|
61
|
+
|
|
62
|
+
The maximum number of connections allowed in the pool before it won't create any more. Default: 15.
|
|
63
|
+
|
|
64
|
+
* (optional) **`shrinkPool`**: <`boolean`>
|
|
65
|
+
|
|
66
|
+
Whether the number of connections should be reduced to `initialSize` when they are returned to the pool. Default: true.
|
|
67
|
+
|
|
68
|
+
* (optional) **`connectionTimeout`**: <`number`>
|
|
69
|
+
|
|
70
|
+
The number of seconds for a connection to remain idle before closing. Default: 3.
|
|
71
|
+
|
|
72
|
+
* (optional) **`loginTimeout`**: <`number`>
|
|
73
|
+
|
|
74
|
+
The number of seconds for an attempt to create a connection before returning to the application. Default: 3.
|
|
75
|
+
|
|
76
|
+
* (optional) **`syntaxChecker`**: <`boolean`>
|
|
77
|
+
|
|
78
|
+
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 `parsedSql`. Default: false.
|
|
79
|
+
|
|
80
|
+
* (optional) **`syntax`**: <`string`>
|
|
81
|
+
|
|
82
|
+
Dropdown list of the available [SQL flavors available](https://www.npmjs.com/package/node-sql-parser#supported-database-sql-syntax). Default: mysql.
|
|
83
|
+
|
|
84
|
+
### `odbc`
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
#### Properties
|
|
89
|
+
|
|
90
|
+
* (**required**) **`connection`**: <`odbc config`>
|
|
91
|
+
|
|
92
|
+
The ODBC pool node that defines the connection settings and manages the connection pool used by this node.
|
|
93
|
+
|
|
94
|
+
* (optional) **`query`**: <`string`>
|
|
95
|
+
|
|
96
|
+
A valid SQL query string that can optionally contains parameters inserted using the Mustache syntax. For exemple, msg.payload can be inserted anywhere in the string using triple curly brackets: `{{{payload}}}`. The node will accept a query that is passed either as msg.query, msg.payload.query or msg.payload if payload is a stringified JSON containing a query key/value pair. A query string passed from the input will override any query defined in the node properties. Mustache syntax cannot be used with a query string passed from the input.
|
|
97
|
+
|
|
98
|
+
Alternatively, the query string can be constructed as a prepared statement; that is with variables replaced by question marks: `SELECT * FROM test WHERE id = ?`. The variables must then be passed to the input using `msg.parameters`. This object must be an array containing the same number of element that there are `?` in the query string. The parameters are inserted one by one, from left to right.
|
|
99
|
+
|
|
100
|
+
* (**required**) **`result to`**: <`dot-notation string`>
|
|
101
|
+
|
|
102
|
+
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.` (ex: `payload.results`) and must not start or end with a period. Square braquet notation is not allowed. The node input object is carried out to the output, as long as the output object name does not conflict 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.
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
|
|
106
|
+
- `input msg: {"payload": {"result": {"othervalue": 10} } };`
|
|
107
|
+
|
|
108
|
+
- `result to: payload.results.values`
|
|
109
|
+
|
|
110
|
+
In this case, `values` will be appended to `result` wihout overwriting `othervalue`. If `result` had been a string, then it would have replaced by `values`.
|
|
111
|
+
|
|
112
|
+
#### Inputs
|
|
113
|
+
|
|
114
|
+
The `odbc` node accepts a message input that is either:
|
|
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>'`
|
|
119
|
+
|
|
120
|
+
* (optional) **`parameters`** <`array`>:
|
|
121
|
+
|
|
122
|
+
An array containing the same number of element that there are `?` in the query string. The array is optionnal only if there are no variables markers in the query string.
|
|
123
|
+
|
|
124
|
+
#### Outputs
|
|
125
|
+
|
|
126
|
+
Returns a message containing an `output object` matching the `result to` dot-notation string as described above.
|
|
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
|
+
|
|
134
|
+
Contains any key/value pairs that were in the original output and were the key was not an integer. The module odbc.js returns a [few useful parameters](https://www.npmjs.com/package/odbc#result-array) but these parameters are not part of the output array and are thus segregated into `msg.odbc`. This is to avoid potential issues if looping through the output array using `Object.entries`.
|
package/odbc.html
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
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
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<script type="text/html" data-template-name="odbc config">
|
|
33
|
+
|
|
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
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<script type="text/javascript">
|
|
96
|
+
RED.nodes.registerType('odbc',{
|
|
97
|
+
category: 'storage',
|
|
98
|
+
color: '#89A5C0',
|
|
99
|
+
defaults: {
|
|
100
|
+
name: {value:""},
|
|
101
|
+
connection: {type:"odbc config", required:true},
|
|
102
|
+
query: {value: ""},
|
|
103
|
+
parameters: {value: ""},
|
|
104
|
+
outputObj: {value:"payload"}
|
|
105
|
+
},
|
|
106
|
+
inputs:1,
|
|
107
|
+
outputs:1,
|
|
108
|
+
icon: "db.svg",
|
|
109
|
+
label: function() {
|
|
110
|
+
return this.name||"odbc";
|
|
111
|
+
},
|
|
112
|
+
oneditprepare: function() {
|
|
113
|
+
this.editor = RED.editor.createEditor({
|
|
114
|
+
id: 'node-input-query-editor',
|
|
115
|
+
mode: 'ace/mode/sql',
|
|
116
|
+
value: this.query
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
oneditsave: function() {
|
|
120
|
+
this.query = this.editor.getValue();
|
|
121
|
+
this.editor.destroy();
|
|
122
|
+
delete this.editor;
|
|
123
|
+
},
|
|
124
|
+
oneditcancel: function() {
|
|
125
|
+
this.editor.destroy();
|
|
126
|
+
delete this.editor;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
<script type="text/html" data-template-name="odbc">
|
|
132
|
+
|
|
133
|
+
<div class="form-row">
|
|
134
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
135
|
+
<input type="text" id="node-input-name">
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div class="form-row">
|
|
139
|
+
<label for="node-input-connection"><i class="fa fa-cog"></i> Connection</label>
|
|
140
|
+
<input type="text" id="node-input-connection">
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="form-row node-text-editor-row">
|
|
144
|
+
<label for="node-input-query" style="width: 100% !important;"><i class="fa fa-search"></i> Query</label>
|
|
145
|
+
<div style="height: 250px;" class="node-text-editor" id="node-input-query-editor" ></div>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="form-row">
|
|
148
|
+
<label for="node-input-outputObj"><i class="fa fa-edit"></i> Result to</label>
|
|
149
|
+
<span>msg.</span><input type="text" id="node-input-outputObj" placeholder="payload" style="width: 64%;">
|
|
150
|
+
</div>
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
<script type="text/markdown" data-help-name="odbc config">
|
|
154
|
+
A configuration node that manages connections in an `odbc.pool` object. [Can take any configuration property recognized by `odbc.pool()`](https://www.npmjs.com/package/odbc/v/2.4.8#constructor-odbcconnectconnectionstring). The connection pool will initialize the first time an `odbc` node receives an input message.
|
|
155
|
+
|
|
156
|
+
## Properties
|
|
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>
|
|
198
|
+
|
|
199
|
+
<script type="text/markdown" data-help-name="odbc">
|
|
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.
|
|
201
|
+
|
|
202
|
+
## Properties
|
|
203
|
+
|
|
204
|
+
* (**required**) **`connection`**: <`odbc config`>
|
|
205
|
+
|
|
206
|
+
The ODBC pool node that defines the connection settings and manages the connection pool used by this node.
|
|
207
|
+
|
|
208
|
+
* (optional) **`query`**: <`string`>
|
|
209
|
+
|
|
210
|
+
A valid SQL query string that can optionally contains parameters inserted using the Mustache syntax. For exemple, msg.payload can be inserted anywhere in the string using triple curly brackets: `{{{payload}}}`. The node will accept a query that is passed either as msg.query, msg.payload.query or msg.payload if payload is a stringified JSON containing a query key/value pair. A query string passed from the input will override any query defined in the node properties. Mustache syntax cannot be used with a query string passed from the input.
|
|
211
|
+
|
|
212
|
+
Alternatively, the query string can be constructed as a prepared statement; that is with variables replaced by question marks: `SELECT * FROM test WHERE id = ?`. The variables must then be passed to the input using `msg.parameters`. This object must be an array containing the same number of element that there are `?` in the query string. The parameters are inserted one by one, from left to right.
|
|
213
|
+
|
|
214
|
+
* (**required**) **`result to`**: <`dot-notation string`>
|
|
215
|
+
|
|
216
|
+
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.` (ex: `payload.results`) and must not start or end with a period. Square braquet notation is not allowed. The node input object is carried out to the output, as long as the output object name does not conflict 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.
|
|
217
|
+
|
|
218
|
+
Example:
|
|
219
|
+
|
|
220
|
+
- `input msg: {"payload": {"result": {"othervalue": 10} } };`
|
|
221
|
+
|
|
222
|
+
- `result to: payload.results.values`
|
|
223
|
+
|
|
224
|
+
In this case, `values` will be appended to `result` wihout overwriting `othervalue`. If `result` had been a string, then it would have replaced by `values`.
|
|
225
|
+
|
|
226
|
+
## Inputs
|
|
227
|
+
|
|
228
|
+
The `odbc` node accepts a message input that is either:
|
|
229
|
+
|
|
230
|
+
- 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>'}"`
|
|
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>'`
|
|
233
|
+
|
|
234
|
+
* (optional) **`parameters`** <`array`>:
|
|
235
|
+
|
|
236
|
+
An array containing the same number of element that there are `?` in the query string. The array is optionnal only if there are no variables markers in the query string.
|
|
237
|
+
|
|
238
|
+
## Outputs
|
|
239
|
+
|
|
240
|
+
Returns a message containing an `output object` matching the `result to` dot-notation string as described above.
|
|
241
|
+
|
|
242
|
+
* **`output object`**: <`array`>
|
|
243
|
+
|
|
244
|
+
The [`odbc` result array](https://www.npmjs.com/package/odbc#result-array) returned from the query.
|
|
245
|
+
|
|
246
|
+
* **`odbc`**: <`object`>
|
|
247
|
+
|
|
248
|
+
Contains any key/value pairs that were in the original output and were the key was not an integer. The module odbc.js returns a [few useful parameters](https://www.npmjs.com/package/odbc#result-array) but these parameters are not part of the output array and are thus segregated into `msg.odbc`. This is to avoid potential issues if looping through the output array using `Object.entries`.
|
|
249
|
+
</script>
|
package/odbc.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
const odbcModule = require('odbc');
|
|
3
|
+
const mustache = require('mustache');
|
|
4
|
+
const objPath = require('object-path');
|
|
5
|
+
//----poolConfig is the function associated to the odbc config node.
|
|
6
|
+
function poolConfig(config){
|
|
7
|
+
RED.nodes.createNode(this, config);
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.pool = null;
|
|
10
|
+
this.connecting = false;
|
|
11
|
+
const thick = this.config.syntaxtick;
|
|
12
|
+
const syntax = this.config.syntax;
|
|
13
|
+
delete this.config.syntaxtick;
|
|
14
|
+
delete this.config.syntax;
|
|
15
|
+
//----Check if the user ticked the syntax checker and if so we create a parser object based on the specified syntax.
|
|
16
|
+
this.parser = ((thick) => {
|
|
17
|
+
if(thick){
|
|
18
|
+
const { Parser } = require('node-sql-parser/build/' + syntax);
|
|
19
|
+
return new Parser();
|
|
20
|
+
} else {return null}
|
|
21
|
+
})(thick)
|
|
22
|
+
//----Converting numeric configuration parameters to integers instead of strings.
|
|
23
|
+
for (let [key,value] of Object.entries(this.config)){
|
|
24
|
+
if (!isNaN(parseInt(value))) this.config[key] = parseInt(value);
|
|
25
|
+
}
|
|
26
|
+
this.connect = async () => {
|
|
27
|
+
if (this.pool == null) { //----Request connection pool.
|
|
28
|
+
this.pool = await odbcModule.pool(this.config);
|
|
29
|
+
this.connecting = false;
|
|
30
|
+
}
|
|
31
|
+
let connection = await this.pool.connect();
|
|
32
|
+
return connection;
|
|
33
|
+
};
|
|
34
|
+
this.on('close', async (removed, done) => {
|
|
35
|
+
if (removed && this?.pool) { await this.pool.close() }
|
|
36
|
+
done();
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
//----Registering the odbc config node to the poolConfig function.
|
|
40
|
+
RED.nodes.registerType('odbc config', poolConfig);
|
|
41
|
+
|
|
42
|
+
//----odbc is the function associated to the odbc node.
|
|
43
|
+
function odbc(config){
|
|
44
|
+
RED.nodes.createNode(this, config);
|
|
45
|
+
this.config = config;
|
|
46
|
+
//----Retrieving the specific configuration node.
|
|
47
|
+
|
|
48
|
+
this.poolNode = RED.nodes.getNode(this.config.connection);
|
|
49
|
+
this.name = this.config.name;
|
|
50
|
+
|
|
51
|
+
//The runQuery function handles the actual query to the database using odbc.js.
|
|
52
|
+
this.runQuery = async function(msg, send, done) {
|
|
53
|
+
try {
|
|
54
|
+
this.status({fill:"blue",shape:"dot",text:"querying..."});
|
|
55
|
+
//---If the output object was passed in the msg, it overwrites to output in the config.
|
|
56
|
+
this.config.outputObj = msg?.output || this.config?.outputObj;
|
|
57
|
+
//---Re-fetching queryString here since it can change because of Mustache even if the node itself doesn't change.
|
|
58
|
+
this.queryString = this.config.query;
|
|
59
|
+
if(this.queryString.length){
|
|
60
|
+
//---Testing if all mustache tags were provided.
|
|
61
|
+
for(var parsed of mustache.parse(this.queryString)){
|
|
62
|
+
if(parsed[0] == "name" || parsed[0] == "&"){
|
|
63
|
+
if(!objPath.has(msg, parsed[1])){
|
|
64
|
+
this.warn(`mustache parameter "${parsed[1]}" is absent and will render to undefined`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//---Replace the mustaches tags with the appropriate values from the input msg.
|
|
69
|
+
this.queryString = mustache.render(this.queryString, msg);
|
|
70
|
+
}
|
|
71
|
+
//----Case were the query is in the message, not in the payload.
|
|
72
|
+
if (msg?.query) this.queryString = msg.query || this.queryString
|
|
73
|
+
//---Handles the case were the received message is a stringified JSON containing a valid query.
|
|
74
|
+
else if (msg?.payload) {
|
|
75
|
+
if (typeof msg.payload == 'string'){
|
|
76
|
+
let payloadJson = null;
|
|
77
|
+
try { payloadJson = JSON.parse(msg.payload) }
|
|
78
|
+
catch (err) {}//---Do nothing, if the payload was not a JSON it will be handled below.
|
|
79
|
+
//----If the payload, which is now equivalent to a non-stringified incoming message, contains a query string.
|
|
80
|
+
if(typeof payloadJson == 'object'){
|
|
81
|
+
if(payloadJson?.query){
|
|
82
|
+
if(typeof payloadJson.query != 'string'){
|
|
83
|
+
this.status({fill: "red", shape: "ring",text: "Invalid query"});
|
|
84
|
+
throw new Error("object msg.payload.query must be a string");
|
|
85
|
+
} else {
|
|
86
|
+
//---A query string coming from an input message as priority over the query defined in Node.
|
|
87
|
+
this.queryString = payloadJson.query || this.queryString;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
if (msg?.payload?.query){
|
|
93
|
+
if(typeof msg.payload.query != 'string'){
|
|
94
|
+
this.status({fill: "red", shape: "ring",text: "Invalid query"});
|
|
95
|
+
throw new Error("object msg.payload.query must be a string");
|
|
96
|
+
} else { this.queryString = msg.payload.query || this.queryString }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//----Case were the query is in the payload, not in the messsage.
|
|
101
|
+
|
|
102
|
+
//----Case were there was no query pased to the node.
|
|
103
|
+
if(!this.queryString){
|
|
104
|
+
this.status({fill: "red", shape: "ring",text: "Invalid query"});
|
|
105
|
+
throw new Error("no query to execute");
|
|
106
|
+
}
|
|
107
|
+
if(this.queryString.includes('\?') && !msg?.parameters){
|
|
108
|
+
this.status({fill: "red", shape: "ring",text: "Invalid statement"});
|
|
109
|
+
throw new Error("the query string includes ? markers but no msg.params was provided");
|
|
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
|
+
}
|
|
168
|
+
}
|
|
169
|
+
objPath.set(msg,this.config.outputObj,result);
|
|
170
|
+
if(this?.parseSql) msg.parsedQuery = this.parseSql;
|
|
171
|
+
if(Object.keys(otherParams).length) msg.odbc = otherParams;
|
|
172
|
+
this.status({fill:'blue',shape:'dot',text:'finish'});
|
|
173
|
+
send(msg);
|
|
174
|
+
} else {
|
|
175
|
+
this.status({fill: "red", shape: "ring",text: "query error"});
|
|
176
|
+
throw new Error("the query returns a falsy");
|
|
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
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bkmj/node-red-contrib-odbcmj",
|
|
3
|
+
"version": "1.2.4",
|
|
4
|
+
"description": "Node Red implementation of odbc.js",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"node-red",
|
|
7
|
+
"odbc",
|
|
8
|
+
"SQL",
|
|
9
|
+
"query"
|
|
10
|
+
],
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"mustache": "^4.2.0",
|
|
13
|
+
"node-sql-parser": "^4.17.0",
|
|
14
|
+
"object-path": "^0.11.8",
|
|
15
|
+
"odbc": "^2.4.8"
|
|
16
|
+
},
|
|
17
|
+
"node-red": {
|
|
18
|
+
"nodes": {
|
|
19
|
+
"odbc": "odbc.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
24
|
+
},
|
|
25
|
+
"license": "Apache 2",
|
|
26
|
+
"author": "Marc-André Morneau <ma.morneau.1@gmail.com>",
|
|
27
|
+
"main": "odbc.js",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/mamorneau/node-red-contrib-odbcmj.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/mamorneau/node-red-contrib-odbcmj/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/mamorneau/node-red-contrib-odbcmj#readme"
|
|
36
|
+
}
|