2019-11-19 00:36:56 -08:00
import { IExecuteFunctions } from 'n8n-core' ;
import {
IDataObject ,
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
2021-04-16 09:33:36 -07:00
NodeOperationError ,
2019-11-19 00:36:56 -08:00
} from 'n8n-workflow' ;
2019-12-12 07:57:59 -08:00
// @ts-ignore
import * as mysql2 from 'mysql2/promise' ;
2019-11-19 00:36:56 -08:00
import { copyInputItems } from './GenericFunctions' ;
2019-12-12 18:24:55 -08:00
export class MySql implements INodeType {
2019-11-19 00:36:56 -08:00
description : INodeTypeDescription = {
displayName : 'MySQL' ,
2019-12-12 18:24:55 -08:00
name : 'mySql' ,
2021-06-12 12:00:37 -07:00
icon : 'file:mysql.svg' ,
2019-11-19 00:36:56 -08:00
group : [ 'input' ] ,
version : 1 ,
2021-07-03 05:40:16 -07:00
description : 'Get, add and update data in MySQL' ,
2019-11-19 00:36:56 -08:00
defaults : {
name : 'MySQL' ,
color : '#4279a2' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
credentials : [
{
2019-12-12 18:24:55 -08:00
name : 'mySql' ,
2019-11-19 00:36:56 -08:00
required : true ,
2020-10-22 06:46:03 -07:00
} ,
2019-11-19 00:36:56 -08:00
] ,
properties : [
{
displayName : 'Operation' ,
name : 'operation' ,
type : 'options' ,
options : [
{
name : 'Execute Query' ,
value : 'executeQuery' ,
2020-07-24 03:56:41 -07:00
description : 'Execute an SQL query.' ,
2019-11-19 00:36:56 -08:00
} ,
{
name : 'Insert' ,
value : 'insert' ,
description : 'Insert rows in database.' ,
} ,
{
name : 'Update' ,
value : 'update' ,
2020-07-24 03:56:41 -07:00
description : 'Update rows in database.' ,
2019-11-19 00:36:56 -08:00
} ,
] ,
default : 'insert' ,
description : 'The operation to perform.' ,
} ,
// ----------------------------------
// executeQuery
// ----------------------------------
{
displayName : 'Query' ,
name : 'query' ,
type : 'string' ,
typeOptions : {
2021-09-11 03:58:48 -07:00
alwaysOpenEditWindow : true ,
2019-11-19 00:36:56 -08:00
} ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'executeQuery' ,
2019-11-19 00:36:56 -08:00
] ,
} ,
} ,
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 : [
2020-10-22 06:46:03 -07:00
'insert' ,
2019-11-19 00:36:56 -08:00
] ,
} ,
} ,
default : '' ,
required : true ,
description : 'Name of the table in which to insert data to.' ,
} ,
{
displayName : 'Columns' ,
name : 'columns' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'insert' ,
2019-11-19 00:36:56 -08:00
] ,
} ,
} ,
default : '' ,
placeholder : 'id,name,description' ,
description : 'Comma separated list of the properties which should used as columns for the new rows.' ,
} ,
2021-04-13 09:24:30 -07:00
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
displayOptions : {
show : {
operation : [
'insert' ,
] ,
} ,
} ,
default : { } ,
placeholder : 'Add modifiers' ,
description : 'Modifiers for INSERT statement.' ,
options : [
{
displayName : 'Ignore' ,
name : 'ignore' ,
type : 'boolean' ,
default : true ,
description : 'Ignore any ignorable errors that occur while executing the INSERT statement.' ,
} ,
{
displayName : 'Priority' ,
name : 'priority' ,
type : 'options' ,
options : [
{
name : 'Low Prioirity' ,
value : 'LOW_PRIORITY' ,
description : 'Delays execution of the INSERT until no other clients are reading from the table.' ,
} ,
{
name : 'High Priority' ,
value : 'HIGH_PRIORITY' ,
description : 'Overrides the effect of the --low-priority-updates option if the server was started with that option. It also causes concurrent inserts not to be used.' ,
} ,
] ,
default : 'LOW_PRIORITY' ,
description : 'Ignore any ignorable errors that occur while executing the INSERT statement.' ,
} ,
] ,
} ,
2019-11-19 00:36:56 -08:00
// ----------------------------------
// update
// ----------------------------------
{
displayName : 'Table' ,
name : 'table' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'update' ,
2019-11-19 00:36:56 -08:00
] ,
} ,
} ,
default : '' ,
required : true ,
description : 'Name of the table in which to update data in' ,
} ,
{
displayName : 'Update Key' ,
name : 'updateKey' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'update' ,
2019-11-19 00:36:56 -08:00
] ,
} ,
} ,
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 : [
2020-10-22 06:46:03 -07:00
'update' ,
2019-11-19 00:36:56 -08:00
] ,
} ,
} ,
default : '' ,
placeholder : 'name,description' ,
description : 'Comma separated list of the properties which should used as columns for rows to update.' ,
} ,
2020-10-22 06:46:03 -07:00
] ,
2019-11-19 00:36:56 -08:00
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
2021-08-20 09:57:30 -07:00
const credentials = await this . getCredentials ( 'mySql' ) ;
2019-11-19 00:36:56 -08:00
if ( credentials === undefined ) {
2021-04-16 09:33:36 -07:00
throw new NodeOperationError ( this . getNode ( ) , 'No credentials got returned!' ) ;
2019-11-19 00:36:56 -08:00
}
2021-04-14 04:43:12 -07:00
// Destructuring SSL configuration
const {
ssl ,
caCertificate ,
clientCertificate ,
clientPrivateKey ,
. . . baseCredentials
} = credentials ;
if ( ssl ) {
baseCredentials . ssl = { } ;
if ( caCertificate ) {
baseCredentials . ssl . ca = caCertificate ;
}
// client certificates might not be required
if ( clientCertificate || clientPrivateKey ) {
baseCredentials . ssl . cert = clientCertificate ;
baseCredentials . ssl . key = clientPrivateKey ;
}
}
const connection = await mysql2 . createConnection ( baseCredentials ) ;
2019-11-19 00:36:56 -08:00
const items = this . getInputData ( ) ;
const operation = this . getNodeParameter ( 'operation' , 0 ) as string ;
let returnItems = [ ] ;
if ( operation === 'executeQuery' ) {
// ----------------------------------
// executeQuery
// ----------------------------------
2021-07-19 23:58:54 -07:00
try {
const queryQueue = items . map ( ( item , index ) = > {
const rawQuery = this . getNodeParameter ( 'query' , index ) as string ;
2019-11-19 00:36:56 -08:00
2021-07-19 23:58:54 -07:00
return connection . query ( rawQuery ) ;
} ) ;
2019-11-19 00:36:56 -08:00
2021-07-19 23:58:54 -07:00
const queryResult = ( await Promise . all ( queryQueue ) as mysql2 . OkPacket [ ] [ ] ) . reduce ( ( collection , result ) = > {
const [ rows , fields ] = result ;
2019-12-12 18:24:55 -08:00
2021-07-19 23:58:54 -07:00
if ( Array . isArray ( rows ) ) {
return collection . concat ( rows ) ;
}
2019-11-19 00:36:56 -08:00
2021-07-19 23:58:54 -07:00
collection . push ( rows ) ;
2019-11-19 00:36:56 -08:00
2021-07-19 23:58:54 -07:00
return collection ;
} , [ ] ) ;
2019-11-19 00:36:56 -08:00
2021-07-19 23:58:54 -07:00
returnItems = this . helpers . returnJsonArray ( queryResult as unknown as IDataObject [ ] ) ;
2019-11-19 00:36:56 -08:00
2021-07-19 23:58:54 -07:00
} catch ( error ) {
if ( this . continueOnFail ( ) ) {
returnItems = this . helpers . returnJsonArray ( { error : error.message } ) ;
} else {
await connection . end ( ) ;
throw error ;
}
}
2019-11-19 00:36:56 -08:00
} else if ( operation === 'insert' ) {
// ----------------------------------
// insert
// ----------------------------------
2021-07-19 23:58:54 -07:00
try {
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 options = this . getNodeParameter ( 'options' , 0 ) as IDataObject ;
const insertIgnore = options . ignore as boolean ;
const insertPriority = options . priority as string ;
2021-09-11 03:58:48 -07:00
2021-07-19 23:58:54 -07:00
const insertSQL = ` INSERT ${ insertPriority || '' } ${ insertIgnore ? 'IGNORE' : '' } INTO ${ table } ( ${ columnString } ) VALUES ${ items . map ( item = > insertPlaceholder ) . join ( ',' ) } ; ` ;
const queryItems = insertItems . reduce ( ( collection , item ) = > collection . concat ( Object . values ( item as any ) ) , [ ] ) ; // tslint:disable-line:no-any
2021-09-11 03:58:48 -07:00
2021-07-19 23:58:54 -07:00
const queryResult = await connection . query ( insertSQL , queryItems ) ;
2021-09-11 03:58:48 -07:00
2021-07-19 23:58:54 -07:00
returnItems = this . helpers . returnJsonArray ( queryResult [ 0 ] as unknown as IDataObject ) ;
} catch ( error ) {
if ( this . continueOnFail ( ) ) {
returnItems = this . helpers . returnJsonArray ( { error : error.message } ) ;
} else {
await connection . end ( ) ;
throw error ;
}
}
2019-11-19 00:36:56 -08:00
} else if ( operation === 'update' ) {
// ----------------------------------
// update
// ----------------------------------
2021-07-19 23:58:54 -07:00
try {
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 ( ) ) ;
2019-11-19 00:36:56 -08:00
2021-07-19 23:58:54 -07:00
if ( ! columns . includes ( updateKey ) ) {
columns . unshift ( updateKey ) ;
}
2019-11-19 00:36:56 -08:00
2021-07-19 23:58:54 -07:00
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 ] ) ) ) ;
const queryResult = await Promise . all ( queryQueue ) ;
returnItems = this . helpers . returnJsonArray ( queryResult . map ( result = > result [ 0 ] ) as unknown as IDataObject [ ] ) ;
} catch ( error ) {
if ( this . continueOnFail ( ) ) {
returnItems = this . helpers . returnJsonArray ( { error : error.message } ) ;
} else {
await connection . end ( ) ;
throw error ;
}
}
2019-11-19 00:36:56 -08:00
} else {
2021-07-19 23:58:54 -07:00
if ( this . continueOnFail ( ) ) {
returnItems = this . helpers . returnJsonArray ( { error : ` The operation " ${ operation } " is not supported! ` } ) ;
} else {
await connection . end ( ) ;
throw new NodeOperationError ( this . getNode ( ) , ` The operation " ${ operation } " is not supported! ` ) ;
}
2019-11-19 00:36:56 -08:00
}
2020-04-07 23:43:16 -07:00
await connection . end ( ) ;
2019-11-19 00:36:56 -08:00
return this . prepareOutputData ( returnItems ) ;
}
}