@rudsys/n8n-nodes-sqlite3 0.1.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/LICENSE.md +21 -0
- package/README.md +79 -0
- package/dist/credentials/SqliteSsh.credentials.js +95 -0
- package/dist/nodes/SqliteSsh/SqliteSsh.node.js +321 -0
- package/package.json +36 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 RudSys
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# n8n-nodes-sqlite3
|
|
2
|
+
|
|
3
|
+
This is an n8n community node to perform CRUD operations on a **Remote SQLite Database** via **SSH**.
|
|
4
|
+
|
|
5
|
+
It allows you to manage SQLite databases hosted on remote servers (e.g., VPS, Shared Hosting) without downloading the file.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
1. **SSH Access** to the remote server.
|
|
10
|
+
2. **`sqlite3`** binary must be installed and available in the PATH on the remote server.
|
|
11
|
+
- Test it by running `ssh user@host "sqlite3 --version"`
|
|
12
|
+
- If it's not in PATH, you might need to symlink it or use the full path (currently not configurable, defaults to `sqlite3`).
|
|
13
|
+
|
|
14
|
+
## Operations
|
|
15
|
+
|
|
16
|
+
### 1. Execute Query
|
|
17
|
+
|
|
18
|
+
Run any raw SQL query. Useful for complex `JOIN`s, `GROUP BY`, or subqueries.
|
|
19
|
+
|
|
20
|
+
- **Query:** Type your SQL (e.g., `SELECT * FROM users JOIN orders ON users.id = orders.user_id`).
|
|
21
|
+
|
|
22
|
+
### 2. Insert
|
|
23
|
+
|
|
24
|
+
Insert data from n8n items into a table.
|
|
25
|
+
|
|
26
|
+
- **Conflict Mode:**
|
|
27
|
+
- **Abort:** Fail if a constraint is violated (default).
|
|
28
|
+
- **Ignore:** Skip row on conflict (`INSERT OR IGNORE`).
|
|
29
|
+
- **Replace:** Overwrite existing row (`INSERT OR REPLACE`).
|
|
30
|
+
|
|
31
|
+
### 3. Select
|
|
32
|
+
|
|
33
|
+
Read data from a table with advanced options.
|
|
34
|
+
|
|
35
|
+
- **Columns:** Specify columns (e.g., `id, name, email`) or use `*`. Support for aggregates (e.g., `COUNT(*)`).
|
|
36
|
+
- **Distinct:** Toggle `SELECT DISTINCT` to remove duplicates.
|
|
37
|
+
- **Filters:** Add multiple conditions (AND logic):
|
|
38
|
+
- Operators: `=`, `!=`, `<`, `<=`, `>`, `>=`, `LIKE`, `NOT LIKE`, `GLOB`, `IN`, `NOT IN`, `BETWEEN`, `IS NULL`, `IS NOT NULL`.
|
|
39
|
+
- **Sort:** Order results by one or more columns (ASC/DESC).
|
|
40
|
+
- **Limit & Offset:** Control pagination.
|
|
41
|
+
|
|
42
|
+
### 4. Update
|
|
43
|
+
|
|
44
|
+
Update rows in a table based on conditions.
|
|
45
|
+
|
|
46
|
+
- **Filters:** Specify which rows to update (WHERE clause).
|
|
47
|
+
- **Data:** The fields in your input JSON will be set in the database.
|
|
48
|
+
|
|
49
|
+
### 5. Delete
|
|
50
|
+
|
|
51
|
+
Delete data or tables.
|
|
52
|
+
|
|
53
|
+
- **Mode:**
|
|
54
|
+
- **Delete Rows:** Remove specific rows based on Filters.
|
|
55
|
+
- **Drop Table:** Remove the entire table and its structure.
|
|
56
|
+
|
|
57
|
+
### 6. Vacuum
|
|
58
|
+
|
|
59
|
+
- Run `VACUUM` command to rebuild the database file and reclaim space.
|
|
60
|
+
|
|
61
|
+
## Credentials
|
|
62
|
+
|
|
63
|
+
Requires **SQLite SSH Credentials**:
|
|
64
|
+
|
|
65
|
+
- Host
|
|
66
|
+
- Port
|
|
67
|
+
- Username
|
|
68
|
+
- Password OR Private Key
|
|
69
|
+
|
|
70
|
+
## Output Format
|
|
71
|
+
|
|
72
|
+
The node attempts to use `sqlite3 -json` for parsing results.
|
|
73
|
+
|
|
74
|
+
- **JSON (Preferred):** Requires SQLite 3.33.0+ on the remote server.
|
|
75
|
+
- **CSV (Fallback):** Can be selected in "Options" if your remote SQLite version is older. Note: CSV parsing is basic.
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SqliteSsh = void 0;
|
|
4
|
+
class SqliteSsh {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'sqliteSsh';
|
|
7
|
+
this.displayName = 'SQLite SSH Credentials';
|
|
8
|
+
this.documentationUrl = 'https://github.com/rudsys/n8n-nodes-sqlite3';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Host',
|
|
12
|
+
name: 'host',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '',
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
displayName: 'Port',
|
|
19
|
+
name: 'port',
|
|
20
|
+
type: 'number',
|
|
21
|
+
default: 22,
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
displayName: 'Username',
|
|
26
|
+
name: 'username',
|
|
27
|
+
type: 'string',
|
|
28
|
+
default: '',
|
|
29
|
+
required: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
displayName: 'Authentication Method',
|
|
33
|
+
name: 'authMethod',
|
|
34
|
+
type: 'options',
|
|
35
|
+
options: [
|
|
36
|
+
{
|
|
37
|
+
name: 'Password',
|
|
38
|
+
value: 'password',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Private Key',
|
|
42
|
+
value: 'privateKey',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
default: 'password',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
displayName: 'Password',
|
|
49
|
+
name: 'password',
|
|
50
|
+
type: 'string',
|
|
51
|
+
typeOptions: {
|
|
52
|
+
password: true,
|
|
53
|
+
},
|
|
54
|
+
displayOptions: {
|
|
55
|
+
show: {
|
|
56
|
+
authMethod: ['password'],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
default: '',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
displayName: 'Private Key',
|
|
63
|
+
name: 'privateKey',
|
|
64
|
+
type: 'string',
|
|
65
|
+
typeOptions: {
|
|
66
|
+
password: true,
|
|
67
|
+
rows: 4,
|
|
68
|
+
},
|
|
69
|
+
displayOptions: {
|
|
70
|
+
show: {
|
|
71
|
+
authMethod: ['privateKey'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
default: '',
|
|
75
|
+
description: 'The private key content (PEM format)',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
displayName: 'Passphrase',
|
|
79
|
+
name: 'passphrase',
|
|
80
|
+
type: 'string',
|
|
81
|
+
typeOptions: {
|
|
82
|
+
password: true,
|
|
83
|
+
},
|
|
84
|
+
displayOptions: {
|
|
85
|
+
show: {
|
|
86
|
+
authMethod: ['privateKey'],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
default: '',
|
|
90
|
+
description: 'Passphrase for the private key',
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.SqliteSsh = SqliteSsh;
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SqliteSsh = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const node_ssh_1 = require("node-ssh");
|
|
6
|
+
class SqliteSsh {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.description = {
|
|
9
|
+
displayName: 'SQLite SSH',
|
|
10
|
+
name: 'sqliteSsh',
|
|
11
|
+
icon: 'fa:database',
|
|
12
|
+
group: ['transform'],
|
|
13
|
+
version: 1,
|
|
14
|
+
description: 'Execute SQLite operations on a remote server via SSH',
|
|
15
|
+
defaults: {
|
|
16
|
+
name: 'SQLite SSH',
|
|
17
|
+
},
|
|
18
|
+
inputs: ['main'],
|
|
19
|
+
outputs: ['main'],
|
|
20
|
+
credentials: [
|
|
21
|
+
{
|
|
22
|
+
name: 'sqliteSsh',
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
properties: [
|
|
27
|
+
{
|
|
28
|
+
displayName: 'Database Path',
|
|
29
|
+
name: 'databasePath',
|
|
30
|
+
type: 'string',
|
|
31
|
+
default: '',
|
|
32
|
+
placeholder: '/var/www/html/data/database.db',
|
|
33
|
+
required: true,
|
|
34
|
+
description: 'Absolute path to the SQLite database file on the remote server',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
displayName: 'SQLite Binary Path',
|
|
38
|
+
name: 'binaryPath',
|
|
39
|
+
type: 'string',
|
|
40
|
+
default: 'sqlite3',
|
|
41
|
+
placeholder: '/usr/bin/sqlite3',
|
|
42
|
+
description: 'Path to the sqlite3 binary on the remote server',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
displayName: 'Operation',
|
|
46
|
+
name: 'operation',
|
|
47
|
+
type: 'options',
|
|
48
|
+
noDataExpression: true,
|
|
49
|
+
options: [
|
|
50
|
+
{ name: 'Execute Query', value: 'executeQuery', description: 'Execute a raw SQL query' },
|
|
51
|
+
{ name: 'Insert', value: 'insert', description: 'Insert data from input items' },
|
|
52
|
+
{ name: 'Select', value: 'select', description: 'Select data from a table' },
|
|
53
|
+
{ name: 'Update', value: 'update', description: 'Update data in a table' },
|
|
54
|
+
{ name: 'Delete', value: 'delete', description: 'Delete data from a table' },
|
|
55
|
+
{ name: 'Vacuum', value: 'vacuum', description: 'Rebuild the database file' },
|
|
56
|
+
],
|
|
57
|
+
default: 'executeQuery',
|
|
58
|
+
},
|
|
59
|
+
// ------------------ Execute Query ------------------
|
|
60
|
+
{
|
|
61
|
+
displayName: 'Query',
|
|
62
|
+
name: 'query',
|
|
63
|
+
type: 'string',
|
|
64
|
+
typeOptions: { rows: 5 },
|
|
65
|
+
displayOptions: { show: { operation: ['executeQuery'] } },
|
|
66
|
+
default: '',
|
|
67
|
+
placeholder: 'SELECT * FROM users;',
|
|
68
|
+
required: true,
|
|
69
|
+
},
|
|
70
|
+
// ------------------ Table Field ------------------
|
|
71
|
+
{
|
|
72
|
+
displayName: 'Table',
|
|
73
|
+
name: 'table',
|
|
74
|
+
type: 'string',
|
|
75
|
+
displayOptions: { show: { operation: ['insert', 'select', 'update', 'delete'] } },
|
|
76
|
+
default: '',
|
|
77
|
+
required: true,
|
|
78
|
+
},
|
|
79
|
+
// ------------------ Insert Options ------------------
|
|
80
|
+
{
|
|
81
|
+
displayName: 'Conflict Mode',
|
|
82
|
+
name: 'conflictMode',
|
|
83
|
+
type: 'options',
|
|
84
|
+
displayOptions: { show: { operation: ['insert'] } },
|
|
85
|
+
options: [
|
|
86
|
+
{ name: 'Abort (Fail)', value: 'abort' },
|
|
87
|
+
{ name: 'Ignore', value: 'ignore' },
|
|
88
|
+
{ name: 'Replace', value: 'replace' },
|
|
89
|
+
],
|
|
90
|
+
default: 'abort',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
displayName: 'Data Processing Mode',
|
|
94
|
+
name: 'dataMode',
|
|
95
|
+
type: 'options',
|
|
96
|
+
options: [
|
|
97
|
+
{ name: 'Auto-Map Input Data to Columns', value: 'autoMap', description: 'Use all input items fields as column names' },
|
|
98
|
+
{ name: 'Map Specific Columns', value: 'define', description: 'Only use specific fields from input items' },
|
|
99
|
+
],
|
|
100
|
+
displayOptions: { show: { operation: ['insert', 'update'] } },
|
|
101
|
+
default: 'autoMap',
|
|
102
|
+
description: 'Choose whether to map all input fields to columns or select specific ones',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
displayName: 'Columns to Map',
|
|
106
|
+
name: 'specificColumns',
|
|
107
|
+
type: 'string',
|
|
108
|
+
displayOptions: {
|
|
109
|
+
show: {
|
|
110
|
+
operation: ['insert', 'update'],
|
|
111
|
+
dataMode: ['define'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
default: '',
|
|
115
|
+
placeholder: 'id, name, email',
|
|
116
|
+
description: 'Comma-separated list of column names to map from input',
|
|
117
|
+
},
|
|
118
|
+
// ------------------ Select Options ------------------
|
|
119
|
+
{
|
|
120
|
+
displayName: 'Columns',
|
|
121
|
+
name: 'columns',
|
|
122
|
+
type: 'string',
|
|
123
|
+
displayOptions: { show: { operation: ['select'] } },
|
|
124
|
+
default: '*',
|
|
125
|
+
description: 'Comma-separated list of columns',
|
|
126
|
+
},
|
|
127
|
+
// ------------------ Filters ------------------
|
|
128
|
+
{
|
|
129
|
+
displayName: 'Filters',
|
|
130
|
+
name: 'filters',
|
|
131
|
+
type: 'fixedCollection',
|
|
132
|
+
typeOptions: { multipleValues: true },
|
|
133
|
+
displayOptions: {
|
|
134
|
+
show: { operation: ['select', 'update', 'delete'] },
|
|
135
|
+
hide: { deleteMode: ['dropTable'] },
|
|
136
|
+
},
|
|
137
|
+
default: {},
|
|
138
|
+
options: [
|
|
139
|
+
{
|
|
140
|
+
name: 'filter',
|
|
141
|
+
displayName: 'Filter',
|
|
142
|
+
values: [
|
|
143
|
+
{ displayName: 'Column', name: 'column', type: 'string', default: '' },
|
|
144
|
+
{
|
|
145
|
+
displayName: 'Operator',
|
|
146
|
+
name: 'operator',
|
|
147
|
+
type: 'options',
|
|
148
|
+
options: [
|
|
149
|
+
{ name: 'Equal (=)', value: 'equal' },
|
|
150
|
+
{ name: 'Not Equal (!=)', value: 'notEqual' },
|
|
151
|
+
{ name: 'Like', value: 'like' },
|
|
152
|
+
{ name: 'Greater Than (>)', value: 'greater' },
|
|
153
|
+
{ name: 'Less Than (<)', value: 'less' },
|
|
154
|
+
{ name: 'Is Null', value: 'isNull' },
|
|
155
|
+
{ name: 'Is Not Null', value: 'isNotNull' },
|
|
156
|
+
],
|
|
157
|
+
default: 'equal',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
displayName: 'Value',
|
|
161
|
+
name: 'value',
|
|
162
|
+
type: 'string',
|
|
163
|
+
default: '',
|
|
164
|
+
displayOptions: { hide: { operator: ['isNull', 'isNotNull'] } },
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async execute() {
|
|
174
|
+
const items = this.getInputData();
|
|
175
|
+
const returnData = [];
|
|
176
|
+
const credentials = await this.getCredentials('sqliteSsh');
|
|
177
|
+
const operation = this.getNodeParameter('operation', 0);
|
|
178
|
+
const databasePath = this.getNodeParameter('databasePath', 0);
|
|
179
|
+
const binaryPath = this.getNodeParameter('binaryPath', 0, 'sqlite3');
|
|
180
|
+
const ssh = new node_ssh_1.NodeSSH();
|
|
181
|
+
// Helpers for SQL Escaping
|
|
182
|
+
const escapeIdentifier = (id) => `"${id.replace(/"/g, '""')}"`;
|
|
183
|
+
const escapeValue = (v) => {
|
|
184
|
+
if (v === null || v === undefined)
|
|
185
|
+
return 'NULL';
|
|
186
|
+
if (typeof v === 'number')
|
|
187
|
+
return String(v);
|
|
188
|
+
if (typeof v === 'boolean')
|
|
189
|
+
return v ? '1' : '0';
|
|
190
|
+
return `'${String(v).replace(/'/g, "''")}'`;
|
|
191
|
+
};
|
|
192
|
+
const buildWhereClause = (i) => {
|
|
193
|
+
const filters = this.getNodeParameter('filters', i, {});
|
|
194
|
+
if (!filters.filter || filters.filter.length === 0)
|
|
195
|
+
return '';
|
|
196
|
+
const clauses = filters.filter.map((f) => {
|
|
197
|
+
const col = escapeIdentifier(f.column);
|
|
198
|
+
switch (f.operator) {
|
|
199
|
+
case 'equal': return `${col} = ${escapeValue(f.value)}`;
|
|
200
|
+
case 'notEqual': return `${col} != ${escapeValue(f.value)}`;
|
|
201
|
+
case 'like': return `${col} LIKE ${escapeValue(f.value)}`;
|
|
202
|
+
case 'greater': return `${col} > ${escapeValue(f.value)}`;
|
|
203
|
+
case 'less': return `${col} < ${escapeValue(f.value)}`;
|
|
204
|
+
case 'isNull': return `${col} IS NULL`;
|
|
205
|
+
case 'isNotNull': return `${col} IS NOT NULL`;
|
|
206
|
+
default: return '';
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
return ` WHERE ${clauses.join(' AND ')}`;
|
|
210
|
+
};
|
|
211
|
+
try {
|
|
212
|
+
// 1. Establish SSH Connection
|
|
213
|
+
await ssh.connect({
|
|
214
|
+
host: credentials.host,
|
|
215
|
+
port: credentials.port,
|
|
216
|
+
username: credentials.username,
|
|
217
|
+
password: credentials.password,
|
|
218
|
+
privateKey: credentials.privateKey,
|
|
219
|
+
passphrase: credentials.passphrase,
|
|
220
|
+
readyTimeout: 20000,
|
|
221
|
+
});
|
|
222
|
+
// 2. Handle Operations
|
|
223
|
+
if (operation === 'insert') {
|
|
224
|
+
// BULK INSERT (Efficiency)
|
|
225
|
+
const table = escapeIdentifier(this.getNodeParameter('table', 0));
|
|
226
|
+
const conflictMode = this.getNodeParameter('conflictMode', 0, 'abort');
|
|
227
|
+
const verb = conflictMode === 'replace' ? 'INSERT OR REPLACE' : (conflictMode === 'ignore' ? 'INSERT OR IGNORE' : 'INSERT');
|
|
228
|
+
if (items.length > 0) {
|
|
229
|
+
const dataMode = this.getNodeParameter('dataMode', 0, 'autoMap');
|
|
230
|
+
let columnsList = Object.keys(items[0].json);
|
|
231
|
+
if (dataMode === 'define') {
|
|
232
|
+
const specificColumns = this.getNodeParameter('specificColumns', 0, '')
|
|
233
|
+
.split(',')
|
|
234
|
+
.map(c => c.trim())
|
|
235
|
+
.filter(c => c);
|
|
236
|
+
if (specificColumns.length > 0) {
|
|
237
|
+
columnsList = specificColumns;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const columnsSql = columnsList.map(escapeIdentifier).join(', ');
|
|
241
|
+
const valuesSql = items.map(item => {
|
|
242
|
+
return `(${columnsList.map(k => escapeValue(item.json[k])).join(', ')})`;
|
|
243
|
+
}).join(', ');
|
|
244
|
+
const sql = `${verb} INTO ${table} (${columnsSql}) VALUES ${valuesSql};`;
|
|
245
|
+
const result = await ssh.execCommand(`${binaryPath} -json "${databasePath}" '${sql.replace(/'/g, "'\\''")}'`);
|
|
246
|
+
if (result.code !== 0)
|
|
247
|
+
throw new Error(result.stderr || 'SQLite Execution Error');
|
|
248
|
+
items.forEach((_, i) => returnData.push({ json: { success: true }, pairedItem: { item: i } }));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// ITERATIVE OPERATIONS (Select, Update, Delete, Raw Query)
|
|
253
|
+
for (let i = 0; i < items.length; i++) {
|
|
254
|
+
let sql = '';
|
|
255
|
+
const table = this.getNodeParameter('table', i, '') ? escapeIdentifier(this.getNodeParameter('table', i, '')) : '';
|
|
256
|
+
if (operation === 'select') {
|
|
257
|
+
const columns = this.getNodeParameter('columns', i, '*');
|
|
258
|
+
const where = buildWhereClause(i);
|
|
259
|
+
sql = `SELECT ${columns} FROM ${table}${where};`;
|
|
260
|
+
}
|
|
261
|
+
else if (operation === 'update') {
|
|
262
|
+
const where = buildWhereClause(i);
|
|
263
|
+
const dataMode = this.getNodeParameter('dataMode', i, 'autoMap');
|
|
264
|
+
let updateEntries = Object.entries(items[i].json);
|
|
265
|
+
if (dataMode === 'define') {
|
|
266
|
+
const specificColumns = this.getNodeParameter('specificColumns', i, '')
|
|
267
|
+
.split(',')
|
|
268
|
+
.map(c => c.trim())
|
|
269
|
+
.filter(c => c);
|
|
270
|
+
if (specificColumns.length > 0) {
|
|
271
|
+
updateEntries = updateEntries.filter(([k]) => specificColumns.includes(k));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const updateFields = updateEntries
|
|
275
|
+
.map(([k, v]) => `${escapeIdentifier(k)} = ${escapeValue(v)}`)
|
|
276
|
+
.join(', ');
|
|
277
|
+
sql = `UPDATE ${table} SET ${updateFields}${where};`;
|
|
278
|
+
}
|
|
279
|
+
else if (operation === 'delete') {
|
|
280
|
+
const where = buildWhereClause(i);
|
|
281
|
+
sql = `DELETE FROM ${table}${where};`;
|
|
282
|
+
}
|
|
283
|
+
else if (operation === 'executeQuery') {
|
|
284
|
+
sql = this.getNodeParameter('query', i);
|
|
285
|
+
}
|
|
286
|
+
else if (operation === 'vacuum') {
|
|
287
|
+
sql = 'VACUUM;';
|
|
288
|
+
}
|
|
289
|
+
const result = await ssh.execCommand(`${binaryPath} -json "${databasePath}" '${sql.replace(/'/g, "'\\''")}'`);
|
|
290
|
+
if (result.code !== 0)
|
|
291
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), result.stderr || 'SQLite Error', { itemIndex: i });
|
|
292
|
+
if (result.stdout) {
|
|
293
|
+
try {
|
|
294
|
+
const parsed = JSON.parse(result.stdout);
|
|
295
|
+
if (Array.isArray(parsed)) {
|
|
296
|
+
parsed.forEach(row => returnData.push({ json: row, pairedItem: { item: i } }));
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
returnData.push({ json: parsed, pairedItem: { item: i } });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
returnData.push({ json: { output: result.stdout }, pairedItem: { item: i } });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
returnData.push({ json: { success: true }, pairedItem: { item: i } });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
ssh.dispose();
|
|
317
|
+
}
|
|
318
|
+
return [returnData];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
exports.SqliteSsh = SqliteSsh;
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rudsys/n8n-nodes-sqlite3",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "n8n node for Remote SQLite operations via SSH",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"n8n-community-node-package"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "n8n-node build",
|
|
11
|
+
"dev": "n8n-node dev",
|
|
12
|
+
"lint": "n8n-node lint",
|
|
13
|
+
"lint:fix": "n8n-node lint --fix"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"n8n": {
|
|
19
|
+
"n8nNodesApiVersion": 1,
|
|
20
|
+
"nodes": [
|
|
21
|
+
"dist/nodes/SqliteSsh/SqliteSsh.node.js"
|
|
22
|
+
],
|
|
23
|
+
"credentials": [
|
|
24
|
+
"dist/credentials/SqliteSsh.credentials.js"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@n8n/node-cli": "^0.17.0",
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"n8n-workflow": "*",
|
|
31
|
+
"typescript": "^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"node-ssh": "^13.2.1"
|
|
35
|
+
}
|
|
36
|
+
}
|