From 3bdd9096e174ef3f379087a4974571e7f72c73a9 Mon Sep 17 00:00:00 2001 From: Guilherme Almeida Girardi Date: Mon, 10 Feb 2020 13:09:06 -0300 Subject: [PATCH 1/5] Added support for MySQL * In packages/cli/src/Db.ts, conditional test of dbType replaced by a switch; * removeAll() function adapted to not cause an error using MySQL; * Added the cross-env module in the "serve" script in the packages/editor-ui/package.json file. This was done to ensure compatibility between platforms when declaring environment variables. Without it, Windows compilation would give an error, for example; * .idea added to .gitignore (IntelliJ IDEA solutions); --- .gitignore | 1 + packages/cli/config/index.ts | 34 ++++++++- packages/cli/src/Db.ts | 75 ++++++++++++------- packages/cli/src/Interfaces.ts | 2 +- packages/cli/src/TestWebhooks.ts | 36 +++++---- packages/cli/src/databases/index.ts | 2 + .../databases/mysqldb/CredentialsEntity.ts | 44 +++++++++++ .../src/databases/mysqldb/ExecutionEntity.ts | 51 +++++++++++++ .../src/databases/mysqldb/WorkflowEntity.ts | 55 ++++++++++++++ packages/cli/src/databases/mysqldb/index.ts | 3 + packages/editor-ui/package.json | 2 +- 11 files changed, 260 insertions(+), 45 deletions(-) create mode 100644 packages/cli/src/databases/mysqldb/CredentialsEntity.ts create mode 100644 packages/cli/src/databases/mysqldb/ExecutionEntity.ts create mode 100644 packages/cli/src/databases/mysqldb/WorkflowEntity.ts create mode 100644 packages/cli/src/databases/mysqldb/index.ts diff --git a/.gitignore b/.gitignore index 1b2f93c3f3..b3eac39207 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ google-generated-credentials.json _START_PACKAGE .env .vscode +.idea diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts index ab75bb0527..ca9a5d10ca 100644 --- a/packages/cli/config/index.ts +++ b/packages/cli/config/index.ts @@ -8,7 +8,7 @@ const config = convict({ database: { type: { doc: 'Type of database to use', - format: ['sqlite', 'mongodb', 'postgresdb'], + format: ['sqlite', 'mongodb', 'mysqldb', 'postgresdb'], default: 'sqlite', env: 'DB_TYPE' }, @@ -52,6 +52,38 @@ const config = convict({ env: 'DB_POSTGRESDB_USER' }, }, + mysqldb: { + database: { + doc: 'MySQL Database', + format: String, + default: 'n8n', + env: 'DB_MYSQLDB_DATABASE' + }, + host: { + doc: 'MySQL Host', + format: String, + default: 'localhost', + env: 'DB_MYSQLDB_HOST' + }, + password: { + doc: 'MySQL Password', + format: String, + default: '', + env: 'DB_MYSQLDB_PASSWORD' + }, + port: { + doc: 'MySQL Port', + format: Number, + default: 5432, + env: 'DB_MYSQLDB_PORT' + }, + user: { + doc: 'MySQL User', + format: String, + default: 'root', + env: 'DB_MYSQLDB_USER' + }, + }, }, executions: { diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index 58d234ef3c..3864304748 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -18,6 +18,7 @@ import { MongoDb, PostgresDb, SQLite, + MySQLDb, } from './databases'; export let collections: IDatabaseCollections = { @@ -36,33 +37,53 @@ export async function init(synchronize?: boolean): Promise let connectionOptions: ConnectionOptions; let dbNotExistError: string | undefined; - if (dbType === 'mongodb') { - entities = MongoDb; - connectionOptions = { - type: 'mongodb', - url: await GenericHelpers.getConfigValue('database.mongodb.connectionUrl') as string, - useNewUrlParser: true, - }; - } else if (dbType === 'postgresdb') { - dbNotExistError = 'does not exist'; - entities = PostgresDb; - connectionOptions = { - type: 'postgres', - database: await GenericHelpers.getConfigValue('database.postgresdb.database') as string, - host: await GenericHelpers.getConfigValue('database.postgresdb.host') as string, - password: await GenericHelpers.getConfigValue('database.postgresdb.password') as string, - port: await GenericHelpers.getConfigValue('database.postgresdb.port') as number, - username: await GenericHelpers.getConfigValue('database.postgresdb.user') as string, - }; - } else if (dbType === 'sqlite') { - dbNotExistError = 'no such table:'; - entities = SQLite; - connectionOptions = { - type: 'sqlite', - database: path.join(n8nFolder, 'database.sqlite'), - }; - } else { - throw new Error(`The database "${dbType}" is currently not supported!`); + switch (dbType) { + case 'mongodb': + entities = MongoDb; + connectionOptions = { + type: 'mongodb', + url: await GenericHelpers.getConfigValue('database.mongodb.connectionUrl') as string, + useNewUrlParser: true, + }; + break; + + case 'postgresdb': + dbNotExistError = 'does not exist'; + entities = PostgresDb; + connectionOptions = { + type: 'postgres', + database: await GenericHelpers.getConfigValue('database.postgresdb.database') as string, + host: await GenericHelpers.getConfigValue('database.postgresdb.host') as string, + password: await GenericHelpers.getConfigValue('database.postgresdb.password') as string, + port: await GenericHelpers.getConfigValue('database.postgresdb.port') as number, + username: await GenericHelpers.getConfigValue('database.postgresdb.user') as string, + }; + break; + + case 'mysqldb': + dbNotExistError = 'does not exist'; + entities = MySQLDb; + connectionOptions = { + type: 'mysql', + database: await GenericHelpers.getConfigValue('database.mysqldb.database') as string, + host: await GenericHelpers.getConfigValue('database.mysqldb.host') as string, + password: await GenericHelpers.getConfigValue('database.mysqldb.password') as string, + port: await GenericHelpers.getConfigValue('database.mysqldb.port') as number, + username: await GenericHelpers.getConfigValue('database.mysqldb.user') as string + }; + break; + + case 'sqlite': + dbNotExistError = 'no such table:'; + entities = SQLite; + connectionOptions = { + type: 'sqlite', + database: path.join(n8nFolder, 'database.sqlite'), + }; + break; + + default: + throw new Error(`The database "${dbType}" is currently not supported!`); } Object.assign(connectionOptions, { diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index dbc39dccaf..33d7b02e7f 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -87,7 +87,7 @@ export interface ICredentialsDecryptedResponse extends ICredentialsDecryptedDb { id: string; } -export type DatabaseType = 'mongodb' | 'postgresdb' | 'sqlite'; +export type DatabaseType = 'mongodb' | 'postgresdb' | 'mysqldb' | 'sqlite'; export type SaveExecutionDataType = 'all' | 'none'; export interface IExecutionBase { diff --git a/packages/cli/src/TestWebhooks.ts b/packages/cli/src/TestWebhooks.ts index 8631f259ab..12a2ef92e2 100644 --- a/packages/cli/src/TestWebhooks.ts +++ b/packages/cli/src/TestWebhooks.ts @@ -208,26 +208,32 @@ export class TestWebhooks { } const nodeTypes = NodeTypes(); - const findQuery = { - where: { - id: findIn(this.activeWebhooks.getWorkflowIds()) - }, - } as FindManyOptions; + /* + * Here I check first if WorkflowIds is empty. This is done because when entering an empty array for TypeORM's In option, a syntax error is generated in MySQL. + * Because the SQL is: ... FROM `workflow_entity`` WorkflowEntity` WHERE `WorkflowEntity`.`id` IN () + * + * The empty IN function is not accepted in MySQL. + */ + const WorkflowIds = this.activeWebhooks.getWorkflowIds(); + if (WorkflowIds.length > 0) { + const findQuery = { + where: { + id: findIn(WorkflowIds) + }, + } as FindManyOptions; - const workflowsDb = await Db.collections.Workflow!.find(findQuery); - const workflows: Workflow[] = []; - for (const workflowData of workflowsDb) { - const workflow = new Workflow(workflowData.id.toString(), workflowData.nodes, workflowData.connections, workflowData.active, nodeTypes, workflowData.staticData, workflowData.settings); - workflows.push(workflow); + const workflowsDb = await Db.collections.Workflow!.find(findQuery); + const workflows: Workflow[] = []; + for (const workflowData of workflowsDb) { + const workflow = new Workflow(workflowData.id.toString(), workflowData.nodes, workflowData.connections, workflowData.active, nodeTypes, workflowData.staticData, workflowData.settings); + workflows.push(workflow); + } + + return this.activeWebhooks.removeAll(workflows); } - - return this.activeWebhooks.removeAll(workflows); } - } - - let testWebhooksInstance: TestWebhooks | undefined; export function getInstance(): TestWebhooks { diff --git a/packages/cli/src/databases/index.ts b/packages/cli/src/databases/index.ts index 9263d9230f..48ba5c86eb 100644 --- a/packages/cli/src/databases/index.ts +++ b/packages/cli/src/databases/index.ts @@ -1,9 +1,11 @@ import * as MongoDb from './mongodb'; import * as PostgresDb from './postgresdb'; import * as SQLite from './sqlite'; +import * as MySQLDb from './mysqldb'; export { MongoDb, PostgresDb, SQLite, + MySQLDb, }; diff --git a/packages/cli/src/databases/mysqldb/CredentialsEntity.ts b/packages/cli/src/databases/mysqldb/CredentialsEntity.ts new file mode 100644 index 0000000000..e3ef375254 --- /dev/null +++ b/packages/cli/src/databases/mysqldb/CredentialsEntity.ts @@ -0,0 +1,44 @@ +import { + ICredentialNodeAccess, +} from 'n8n-workflow'; + +import { + ICredentialsDb, +} from '../../'; + +import { + Column, + Entity, + Index, + PrimaryGeneratedColumn, +} from 'typeorm'; + +@Entity() +export class CredentialsEntity implements ICredentialsDb { + + @PrimaryGeneratedColumn() + id: number; + + @Column({ + length: 128 + }) + name: string; + + @Column('text') + data: string; + + @Index() + @Column({ + length: 32 + }) + type: string; + + @Column('json') + nodesAccess: ICredentialNodeAccess[]; + + @Column('timestamp') + createdAt: Date; + + @Column('timestamp') + updatedAt: Date; +} diff --git a/packages/cli/src/databases/mysqldb/ExecutionEntity.ts b/packages/cli/src/databases/mysqldb/ExecutionEntity.ts new file mode 100644 index 0000000000..8a7f691f0f --- /dev/null +++ b/packages/cli/src/databases/mysqldb/ExecutionEntity.ts @@ -0,0 +1,51 @@ +import { + WorkflowExecuteMode, +} from 'n8n-workflow'; + +import { + IExecutionFlattedDb, + IWorkflowDb, +} from '../../'; + +import { + Column, + Entity, + Index, + PrimaryGeneratedColumn, +} from 'typeorm'; + + +@Entity() +export class ExecutionEntity implements IExecutionFlattedDb { + + @PrimaryGeneratedColumn() + id: number; + + @Column('text') + data: string; + + @Column() + finished: boolean; + + @Column('varchar') + mode: WorkflowExecuteMode; + + @Column({ nullable: true }) + retryOf: string; + + @Column({ nullable: true }) + retrySuccessId: string; + + @Column('timestamp') + startedAt: Date; + + @Column('timestamp') + stoppedAt: Date; + + @Column('json') + workflowData: IWorkflowDb; + + @Index() + @Column({ nullable: true }) + workflowId: string; +} diff --git a/packages/cli/src/databases/mysqldb/WorkflowEntity.ts b/packages/cli/src/databases/mysqldb/WorkflowEntity.ts new file mode 100644 index 0000000000..3f870b929b --- /dev/null +++ b/packages/cli/src/databases/mysqldb/WorkflowEntity.ts @@ -0,0 +1,55 @@ +import { + IConnections, + IDataObject, + INode, + IWorkflowSettings, +} from 'n8n-workflow'; + +import { + IWorkflowDb, +} from '../../'; + +import { + Column, + Entity, + PrimaryGeneratedColumn, +} from 'typeorm'; + +@Entity() +export class WorkflowEntity implements IWorkflowDb { + + @PrimaryGeneratedColumn() + id: number; + + @Column({ + length: 128 + }) + name: string; + + @Column() + active: boolean; + + @Column('json') + nodes: INode[]; + + @Column('json') + connections: IConnections; + + @Column('timestamp') + createdAt: Date; + + @Column('timestamp') + updatedAt: Date; + + @Column({ + type: 'json', + nullable: true, + }) + settings?: IWorkflowSettings; + + @Column({ + type: 'json', + nullable: true, + }) + staticData?: IDataObject; +} diff --git a/packages/cli/src/databases/mysqldb/index.ts b/packages/cli/src/databases/mysqldb/index.ts new file mode 100644 index 0000000000..164d67fd0c --- /dev/null +++ b/packages/cli/src/databases/mysqldb/index.ts @@ -0,0 +1,3 @@ +export * from './CredentialsEntity'; +export * from './ExecutionEntity'; +export * from './WorkflowEntity'; diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index e2ce2b51f6..ccd63f549c 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -17,7 +17,7 @@ "build": "vue-cli-service build", "dev": "npm run serve", "lint": "vue-cli-service lint", - "serve": "VUE_APP_URL_BASE_API=http://localhost:5678/ vue-cli-service serve", + "serve": "cross-env VUE_APP_URL_BASE_API=http://localhost:5678/ vue-cli-service serve", "test": "npm run test:unit", "tslint": "tslint -p tsconfig.json -c tslint.json", "test:e2e": "vue-cli-service test:e2e", From 9e4b6cc97a85577cfe365500f91bea013271c906 Mon Sep 17 00:00:00 2001 From: Guilherme Almeida Girardi Date: Mon, 10 Feb 2020 13:18:28 -0300 Subject: [PATCH 2/5] Fixed MySQL default port --- packages/cli/config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts index ca9a5d10ca..b54fc33f65 100644 --- a/packages/cli/config/index.ts +++ b/packages/cli/config/index.ts @@ -74,7 +74,7 @@ const config = convict({ port: { doc: 'MySQL Port', format: Number, - default: 5432, + default: 3306, env: 'DB_MYSQLDB_PORT' }, user: { From ff556ebfd96bc54deaef45574d0c79f58513aed3 Mon Sep 17 00:00:00 2001 From: "Guilherme A. Girardi" Date: Mon, 10 Feb 2020 13:27:58 -0300 Subject: [PATCH 3/5] MySQL added in database.md --- docs/database.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/database.md b/docs/database.md index e55669c294..4603f7f2aa 100644 --- a/docs/database.md +++ b/docs/database.md @@ -51,6 +51,30 @@ export DB_POSTGRESDB_PASSWORD=n8n n8n start ``` +## MySQL + +The compatibility with MySQL was tested, even so, it is advisable to observe the operation of the application with this DB, as it is a new option, recently added. If you spot any problems, feel free to submit a PR. + +To use MySQL as database you can provide the following environment variables: + - `DB_TYPE=mysqldb` + - `DB_MYSQLDB_DATABASE` (default: 'n8n') + - `DB_MYSQLDB_HOST` (default: 'localhost') + - `DB_MYSQLDB_PORT` (default: 3306) + - `DB_MYSQLDB_USER` (default: 'root') + - `DB_MYSQLDB_PASSWORD` (default: empty) + + +```bash +export DB_TYPE=postgresdb +export DB_MYSQLDB_DATABASE=n8n +export DB_MYSQLDB_HOST=mysqldb +export DB_MYSQLDB_PORT=3306 +export DB_MYSQLDB_USER=n8n +export DB_MYSQLDB_PASSWORD=n8n + +n8n start +``` + ## SQLite The default database which gets used if no other one is defined. From f3750a6646c6719634c6e02f138a0140c50c5ace Mon Sep 17 00:00:00 2001 From: Guilherme Almeida Girardi Date: Mon, 10 Feb 2020 14:43:21 -0300 Subject: [PATCH 4/5] Type adjustment in MySQL columns * TIMESTAMP type columns have been replaced by DATETIME. Depending on the version of MySQL and SQL_MODE, the DBMS does not accept to create TIMESTAMP NOT NULL columns without a default value; --- packages/cli/src/databases/mysqldb/CredentialsEntity.ts | 4 ++-- packages/cli/src/databases/mysqldb/ExecutionEntity.ts | 4 ++-- packages/cli/src/databases/mysqldb/WorkflowEntity.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/databases/mysqldb/CredentialsEntity.ts b/packages/cli/src/databases/mysqldb/CredentialsEntity.ts index e3ef375254..5654581ff0 100644 --- a/packages/cli/src/databases/mysqldb/CredentialsEntity.ts +++ b/packages/cli/src/databases/mysqldb/CredentialsEntity.ts @@ -36,9 +36,9 @@ export class CredentialsEntity implements ICredentialsDb { @Column('json') nodesAccess: ICredentialNodeAccess[]; - @Column('timestamp') + @Column('datetime') createdAt: Date; - @Column('timestamp') + @Column('datetime') updatedAt: Date; } diff --git a/packages/cli/src/databases/mysqldb/ExecutionEntity.ts b/packages/cli/src/databases/mysqldb/ExecutionEntity.ts index 8a7f691f0f..e0c084fcfc 100644 --- a/packages/cli/src/databases/mysqldb/ExecutionEntity.ts +++ b/packages/cli/src/databases/mysqldb/ExecutionEntity.ts @@ -36,10 +36,10 @@ export class ExecutionEntity implements IExecutionFlattedDb { @Column({ nullable: true }) retrySuccessId: string; - @Column('timestamp') + @Column('datetime') startedAt: Date; - @Column('timestamp') + @Column('datetime') stoppedAt: Date; @Column('json') diff --git a/packages/cli/src/databases/mysqldb/WorkflowEntity.ts b/packages/cli/src/databases/mysqldb/WorkflowEntity.ts index 3f870b929b..4cca4e62a6 100644 --- a/packages/cli/src/databases/mysqldb/WorkflowEntity.ts +++ b/packages/cli/src/databases/mysqldb/WorkflowEntity.ts @@ -35,10 +35,10 @@ export class WorkflowEntity implements IWorkflowDb { @Column('json') connections: IConnections; - @Column('timestamp') + @Column('datetime') createdAt: Date; - @Column('timestamp') + @Column('datetime') updatedAt: Date; @Column({ From 92445082fd2a343faa75061085402be2911ebbfe Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 11 Feb 2020 21:26:43 -0800 Subject: [PATCH 5/5] :zap: Small additions to MySQL support --- docker/images/n8n/README.md | 25 +++++++++++++++++++++++++ docs/database.md | 2 +- packages/cli/package.json | 1 + 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index 16d12e226d..984a024848 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -175,6 +175,31 @@ docker run -it --rm \ A full working setup with docker-compose can be found [here](https://github.com/n8n-io/n8n/blob/master/docker/compose/withPostgres/README.md) +#### Use with MySQL + +Replace the following placeholders with the actual data: + - MYSQLDB_DATABASE + - MYSQLDB_HOST + - MYSQLDB_PASSWORD + - MYSQLDB_PORT + - MYSQLDB_USER + +``` +docker run -it --rm \ + --name n8n \ + -p 5678:5678 \ + -e DB_TYPE=mysqldb \ + -e DB_MYSQLDB_DATABASE= \ + -e DB_MYSQLDB_HOST= \ + -e DB_MYSQLDB_PORT= \ + -e DB_MYSQLDB_USER= \ + -e DB_MYSQLDB_PASSWORD= \ + -v ~/.n8n:/root/.n8n \ + n8nio/n8n \ + n8n start +``` + + ## Passing Sensitive Data via File To avoid passing sensitive information via environment variables "_FILE" may be diff --git a/docs/database.md b/docs/database.md index 4603f7f2aa..1410374a26 100644 --- a/docs/database.md +++ b/docs/database.md @@ -65,7 +65,7 @@ To use MySQL as database you can provide the following environment variables: ```bash -export DB_TYPE=postgresdb +export DB_TYPE=mysqldb export DB_MYSQLDB_DATABASE=n8n export DB_MYSQLDB_HOST=mysqldb export DB_MYSQLDB_PORT=3306 diff --git a/packages/cli/package.json b/packages/cli/package.json index f14916a275..06a0d7fbbf 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -93,6 +93,7 @@ "localtunnel": "^2.0.0", "lodash.get": "^4.4.2", "mongodb": "^3.2.3", + "mysql2": "^2.0.1", "n8n-core": "~0.24.0", "n8n-editor-ui": "~0.35.0", "n8n-nodes-base": "~0.47.0",