Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MySQL node #131

Merged
merged 3 commits into from
Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions packages/nodes-base/credentials/MySQL.credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';


export class MySQL implements ICredentialType {
name = 'mysql';
displayName = 'MySQL';
properties = [
{
displayName: 'Host',
name: 'host',
type: 'string' as NodePropertyTypes,
default: 'localhost',
},
{
displayName: 'Database',
name: 'database',
type: 'string' as NodePropertyTypes,
default: 'mysql',
},
{
displayName: 'User',
name: 'user',
type: 'string' as NodePropertyTypes,
default: 'mysql',
},
{
displayName: 'Password',
name: 'password',
type: 'string' as NodePropertyTypes,
typeOptions: {
password: true,
},
default: '',
},
{
displayName: 'Port',
name: 'port',
type: 'number' as NodePropertyTypes,
default: 3306,
},
];
}
28 changes: 28 additions & 0 deletions packages/nodes-base/nodes/MySQL/GenericFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';

/**
* Returns of copy of the items which only contains the json data and
* of that only the define properties
*
* @param {INodeExecutionData[]} items The items to copy
* @param {string[]} properties The properties it should include
* @returns
*/
export function copyInputItems(items: INodeExecutionData[], properties: string[]): IDataObject[] {
// Prepare the data to insert and copy it to be returned
let newItem: IDataObject;
return items.map((item) => {
newItem = {};
for (const property of properties) {
if (item.json[property] === undefined) {
newItem[property] = null;
} else {
newItem[property] = JSON.parse(JSON.stringify(item.json[property]));
}
}
return newItem;
});
}
253 changes: 253 additions & 0 deletions packages/nodes-base/nodes/MySQL/MySQL.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import { IExecuteFunctions } from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
// @ts-ignore
import * as mysql2 from 'mysql2/promise';

import { copyInputItems } from './GenericFunctions';

export class MySQL implements INodeType {
description: INodeTypeDescription = {
displayName: 'MySQL',
name: 'mysql',
icon: 'file:mysql.png',
group: ['input'],
version: 1,
description: 'Gets, add and update data in MySQL.',
defaults: {
name: 'MySQL',
color: '#4279a2',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'mysql',
required: true,
}
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
options: [
{
name: 'Execute Query',
value: 'executeQuery',
description: 'Executes a SQL query.',
},
{
name: 'Insert',
value: 'insert',
description: 'Insert rows in database.',
},
{
name: 'Update',
value: 'update',
description: 'Updates rows in database.',
},
],
default: 'insert',
description: 'The operation to perform.',
},

// ----------------------------------
// executeQuery
// ----------------------------------
{
displayName: 'Query',
name: 'query',
type: 'string',
typeOptions: {
rows: 5,
},
displayOptions: {
show: {
operation: [
'executeQuery'
],
},
},
default: '',
placeholder: 'SELECT id, name FROM product WHERE id < 40',
required: true,
description: 'The SQL query to execute.',
},


// ----------------------------------
// insert
// ----------------------------------
{
displayName: 'Table',
name: 'table',
type: 'string',
displayOptions: {
show: {
operation: [
'insert'
],
},
},
default: '',
required: true,
description: 'Name of the table in which to insert data to.',
},
{
displayName: 'Columns',
name: 'columns',
type: 'string',
displayOptions: {
show: {
operation: [
'insert'
],
},
},
default: '',
placeholder: 'id,name,description',
description: 'Comma separated list of the properties which should used as columns for the new rows.',
},


// ----------------------------------
// update
// ----------------------------------
{
displayName: 'Table',
name: 'table',
type: 'string',
displayOptions: {
show: {
operation: [
'update'
],
},
},
default: '',
required: true,
description: 'Name of the table in which to update data in',
},
{
displayName: 'Update Key',
name: 'updateKey',
type: 'string',
displayOptions: {
show: {
operation: [
'update'
],
},
},
default: 'id',
required: true,
description: 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".',
},
{
displayName: 'Columns',
name: 'columns',
type: 'string',
displayOptions: {
show: {
operation: [
'update'
],
},
},
default: '',
placeholder: 'name,description',
description: 'Comma separated list of the properties which should used as columns for rows to update.',
},

]
};


async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const credentials = this.getCredentials('mysql');

if (credentials === undefined) {
throw new Error('No credentials got returned!');
}

const connection = await mysql2.createConnection(credentials);
const items = this.getInputData();
const operation = this.getNodeParameter('operation', 0) as string;
let returnItems = [];

if (operation === 'executeQuery') {
// ----------------------------------
// executeQuery
// ----------------------------------

const queryQueue = items.map((item, index) => {
const rawQuery = this.getNodeParameter('query', index) as string;

return connection.query(rawQuery);
});
let queryResult = await Promise.all(queryQueue);

queryResult = queryResult.reduce((collection, result) => {
const [rows, fields] = result;

if (Array.isArray(rows)) {
return collection.concat(rows);
}

collection.push(rows);

return collection;
}, []);

returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]);

} else if (operation === 'insert') {
// ----------------------------------
// insert
// ----------------------------------

const table = this.getNodeParameter('table', 0) as string;
const columnString = this.getNodeParameter('columns', 0) as string;
const columns = columnString.split(',').map(column => column.trim());
const insertItems = copyInputItems(items, columns);
const insertPlaceholder = `(${columns.map(column => '?').join(',')})`;
const insertSQL = `INSERT INTO ${table}(${columnString}) VALUES ${items.map(item => insertPlaceholder).join(',')};`;
const queryItems = insertItems.reduce((collection, item) => collection.concat(Object.values(item as any)), []);
const queryResult = await connection.query(insertSQL, queryItems);

returnItems = this.helpers.returnJsonArray(queryResult[0] as IDataObject);

} else if (operation === 'update') {
// ----------------------------------
// update
// ----------------------------------

const table = this.getNodeParameter('table', 0) as string;
const updateKey = this.getNodeParameter('updateKey', 0) as string;
const columnString = this.getNodeParameter('columns', 0) as string;
const columns = columnString.split(',').map(column => column.trim());

if (!columns.includes(updateKey)) {
columns.unshift(updateKey);
}

const updateItems = copyInputItems(items, columns);
const updateSQL = `UPDATE ${table} SET ${columns.map(column => `${column} = ?`).join(',')} WHERE ${updateKey} = ?;`;
const queryQueue = updateItems.map((item) => connection.query(updateSQL, Object.values(item).concat(item[updateKey])));
let queryResult = await Promise.all(queryQueue);

queryResult = queryResult.map(result => result[0]);
returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]);

} else {
throw new Error(`The operation "${operation}" is not supported!`);
}

return this.prepareOutputData(returnItems);
}
}
Binary file added packages/nodes-base/nodes/MySQL/mysql.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/nodes-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"dist/credentials/MandrillApi.credentials.js",
"dist/credentials/MattermostApi.credentials.js",
"dist/credentials/MongoDb.credentials.js",
"dist/credentials/MySQL.credentials.js",
"dist/credentials/NextCloudApi.credentials.js",
"dist/credentials/OpenWeatherMapApi.credentials.js",
"dist/credentials/PipedriveApi.credentials.js",
Expand Down Expand Up @@ -118,6 +119,7 @@
"dist/nodes/Merge.node.js",
"dist/nodes/MoveBinaryData.node.js",
"dist/nodes/MongoDb/MongoDb.node.js",
"dist/nodes/MySQL/MySQL.node.js",
"dist/nodes/NextCloud/NextCloud.node.js",
"dist/nodes/NoOp.node.js",
"dist/nodes/OpenWeatherMap.node.js",
Expand Down Expand Up @@ -189,6 +191,7 @@
"lodash.set": "^4.3.2",
"lodash.unset": "^4.5.2",
"mongodb": "^3.3.2",
"mysql2": "^2.0.1",
"n8n-core": "~0.17.0",
"nodemailer": "^5.1.1",
"pdf-parse": "^1.1.1",
Expand Down