Merge pull request #4782 from Budibase/fix/mysql2-usage
Updating usage of MySQL2 to use promise methods
This commit is contained in:
commit
f5925925d6
|
@ -0,0 +1,17 @@
|
||||||
|
module MySQLMock {
|
||||||
|
const mysql: any = {}
|
||||||
|
|
||||||
|
const client = {
|
||||||
|
connect: jest.fn(),
|
||||||
|
end: jest.fn(),
|
||||||
|
query: jest.fn(async () => {
|
||||||
|
return [[]]
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql.createConnection = jest.fn(async () => {
|
||||||
|
return client
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = mysql
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ import {
|
||||||
import { DatasourcePlus } from "./base/datasourcePlus"
|
import { DatasourcePlus } from "./base/datasourcePlus"
|
||||||
|
|
||||||
module MySQLModule {
|
module MySQLModule {
|
||||||
const mysql = require("mysql2")
|
const mysql = require("mysql2/promise")
|
||||||
const Sql = require("./base/sql")
|
const Sql = require("./base/sql")
|
||||||
|
|
||||||
interface MySQLConfig {
|
interface MySQLConfig {
|
||||||
|
@ -29,7 +29,7 @@ module MySQLModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCHEMA: Integration = {
|
const SCHEMA: Integration = {
|
||||||
docs: "https://github.com/mysqljs/mysql",
|
docs: "https://github.com/sidorares/node-mysql2",
|
||||||
plus: true,
|
plus: true,
|
||||||
friendlyName: "MySQL",
|
friendlyName: "MySQL",
|
||||||
description:
|
description:
|
||||||
|
@ -80,36 +80,9 @@ module MySQLModule {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function internalQuery(
|
|
||||||
client: any,
|
|
||||||
query: SqlQuery,
|
|
||||||
connect: boolean = true
|
|
||||||
): Promise<any[] | any> {
|
|
||||||
// Node MySQL is callback based, so we must wrap our call in a promise
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (connect) {
|
|
||||||
client.connect()
|
|
||||||
}
|
|
||||||
return client.query(
|
|
||||||
query.sql,
|
|
||||||
query.bindings || {},
|
|
||||||
(error: any, results: object[]) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
resolve(results)
|
|
||||||
}
|
|
||||||
if (connect) {
|
|
||||||
client.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
class MySQLIntegration extends Sql implements DatasourcePlus {
|
class MySQLIntegration extends Sql implements DatasourcePlus {
|
||||||
private config: MySQLConfig
|
private config: MySQLConfig
|
||||||
private readonly client: any
|
private client: any
|
||||||
public tables: Record<string, Table> = {}
|
public tables: Record<string, Table> = {}
|
||||||
public schemaErrors: Record<string, string> = {}
|
public schemaErrors: Record<string, string> = {}
|
||||||
|
|
||||||
|
@ -119,93 +92,127 @@ module MySQLModule {
|
||||||
if (config.ssl && Object.keys(config.ssl).length === 0) {
|
if (config.ssl && Object.keys(config.ssl).length === 0) {
|
||||||
delete config.ssl
|
delete config.ssl
|
||||||
}
|
}
|
||||||
this.client = mysql.createConnection(config)
|
this.config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
this.client = await mysql.createConnection(this.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect() {
|
||||||
|
await this.client.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
async internalQuery(
|
||||||
|
query: SqlQuery,
|
||||||
|
connect: boolean = true
|
||||||
|
): Promise<any[] | any> {
|
||||||
|
try {
|
||||||
|
if (connect) {
|
||||||
|
await this.connect()
|
||||||
|
}
|
||||||
|
// Node MySQL is callback based, so we must wrap our call in a promise
|
||||||
|
const response = await this.client.query(
|
||||||
|
query.sql,
|
||||||
|
query.bindings || []
|
||||||
|
)
|
||||||
|
return response[0]
|
||||||
|
} finally {
|
||||||
|
if (connect) {
|
||||||
|
await this.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
||||||
const tables: { [key: string]: Table } = {}
|
const tables: { [key: string]: Table } = {}
|
||||||
const database = this.config.database
|
const database = this.config.database
|
||||||
this.client.connect()
|
await this.connect()
|
||||||
|
|
||||||
// get the tables first
|
try {
|
||||||
const tablesResp = await internalQuery(
|
// get the tables first
|
||||||
this.client,
|
const tablesResp = await this.internalQuery(
|
||||||
{ sql: "SHOW TABLES;" },
|
{ sql: "SHOW TABLES;" },
|
||||||
false
|
|
||||||
)
|
|
||||||
const tableNames = tablesResp.map(
|
|
||||||
(obj: any) =>
|
|
||||||
obj[`Tables_in_${database}`] ||
|
|
||||||
obj[`Tables_in_${database.toLowerCase()}`]
|
|
||||||
)
|
|
||||||
for (let tableName of tableNames) {
|
|
||||||
const primaryKeys = []
|
|
||||||
const schema: TableSchema = {}
|
|
||||||
const descResp = await internalQuery(
|
|
||||||
this.client,
|
|
||||||
{ sql: `DESCRIBE \`${tableName}\`;` },
|
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
for (let column of descResp) {
|
const tableNames = tablesResp.map(
|
||||||
const columnName = column.Field
|
(obj: any) =>
|
||||||
if (column.Key === "PRI" && primaryKeys.indexOf(column.Key) === -1) {
|
obj[`Tables_in_${database}`] ||
|
||||||
primaryKeys.push(columnName)
|
obj[`Tables_in_${database.toLowerCase()}`]
|
||||||
|
)
|
||||||
|
for (let tableName of tableNames) {
|
||||||
|
const primaryKeys = []
|
||||||
|
const schema: TableSchema = {}
|
||||||
|
const descResp = await this.internalQuery(
|
||||||
|
{ sql: `DESCRIBE \`${tableName}\`;` },
|
||||||
|
false
|
||||||
|
)
|
||||||
|
for (let column of descResp) {
|
||||||
|
const columnName = column.Field
|
||||||
|
if (
|
||||||
|
column.Key === "PRI" &&
|
||||||
|
primaryKeys.indexOf(column.Key) === -1
|
||||||
|
) {
|
||||||
|
primaryKeys.push(columnName)
|
||||||
|
}
|
||||||
|
const constraints = {
|
||||||
|
presence: column.Null !== "YES",
|
||||||
|
}
|
||||||
|
const isAuto: boolean =
|
||||||
|
typeof column.Extra === "string" &&
|
||||||
|
(column.Extra === "auto_increment" ||
|
||||||
|
column.Extra.toLowerCase().includes("generated"))
|
||||||
|
schema[columnName] = {
|
||||||
|
name: columnName,
|
||||||
|
autocolumn: isAuto,
|
||||||
|
type: convertSqlType(column.Type),
|
||||||
|
constraints,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const constraints = {
|
if (!tables[tableName]) {
|
||||||
presence: column.Null !== "YES",
|
tables[tableName] = {
|
||||||
}
|
_id: buildExternalTableId(datasourceId, tableName),
|
||||||
const isAuto: boolean =
|
primary: primaryKeys,
|
||||||
typeof column.Extra === "string" &&
|
name: tableName,
|
||||||
(column.Extra === "auto_increment" ||
|
schema,
|
||||||
column.Extra.toLowerCase().includes("generated"))
|
}
|
||||||
schema[columnName] = {
|
|
||||||
name: columnName,
|
|
||||||
autocolumn: isAuto,
|
|
||||||
type: convertSqlType(column.Type),
|
|
||||||
constraints,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!tables[tableName]) {
|
|
||||||
tables[tableName] = {
|
|
||||||
_id: buildExternalTableId(datasourceId, tableName),
|
|
||||||
primary: primaryKeys,
|
|
||||||
name: tableName,
|
|
||||||
schema,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
await this.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client.end()
|
|
||||||
const final = finaliseExternalTables(tables, entities)
|
const final = finaliseExternalTables(tables, entities)
|
||||||
this.tables = final.tables
|
this.tables = final.tables
|
||||||
this.schemaErrors = final.errors
|
this.schemaErrors = final.errors
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(query: SqlQuery | string) {
|
async create(query: SqlQuery | string) {
|
||||||
const results = await internalQuery(this.client, getSqlQuery(query))
|
const results = await this.internalQuery(getSqlQuery(query))
|
||||||
return results.length ? results : [{ created: true }]
|
return results.length ? results : [{ created: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async read(query: SqlQuery | string) {
|
async read(query: SqlQuery | string) {
|
||||||
return internalQuery(this.client, getSqlQuery(query))
|
return this.internalQuery(getSqlQuery(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(query: SqlQuery | string) {
|
async update(query: SqlQuery | string) {
|
||||||
const results = await internalQuery(this.client, getSqlQuery(query))
|
const results = await this.internalQuery(getSqlQuery(query))
|
||||||
return results.length ? results : [{ updated: true }]
|
return results.length ? results : [{ updated: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(query: SqlQuery | string) {
|
async delete(query: SqlQuery | string) {
|
||||||
const results = await internalQuery(this.client, getSqlQuery(query))
|
const results = await this.internalQuery(getSqlQuery(query))
|
||||||
return results.length ? results : [{ deleted: true }]
|
return results.length ? results : [{ deleted: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(json: QueryJson) {
|
async query(json: QueryJson) {
|
||||||
this.client.connect()
|
await this.connect()
|
||||||
const queryFn = (query: any) => internalQuery(this.client, query, false)
|
try {
|
||||||
const output = await this.queryWithReturning(json, queryFn)
|
const queryFn = (query: any) => this.internalQuery(query, false)
|
||||||
this.client.end()
|
return await this.queryWithReturning(json, queryFn)
|
||||||
return output
|
} finally {
|
||||||
|
await this.disconnect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe("MySQL Integration", () => {
|
||||||
await config.integration.create({
|
await config.integration.create({
|
||||||
sql
|
sql
|
||||||
})
|
})
|
||||||
expect(config.integration.client.query).toHaveBeenCalledWith(sql, {}, expect.any(Function))
|
expect(config.integration.client.query).toHaveBeenCalledWith(sql, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls the read method with the correct params", async () => {
|
it("calls the read method with the correct params", async () => {
|
||||||
|
@ -27,7 +27,7 @@ describe("MySQL Integration", () => {
|
||||||
await config.integration.read({
|
await config.integration.read({
|
||||||
sql
|
sql
|
||||||
})
|
})
|
||||||
expect(config.integration.client.query).toHaveBeenCalledWith(sql, {}, expect.any(Function))
|
expect(config.integration.client.query).toHaveBeenCalledWith(sql, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls the update method with the correct params", async () => {
|
it("calls the update method with the correct params", async () => {
|
||||||
|
@ -35,7 +35,7 @@ describe("MySQL Integration", () => {
|
||||||
await config.integration.update({
|
await config.integration.update({
|
||||||
sql
|
sql
|
||||||
})
|
})
|
||||||
expect(config.integration.client.query).toHaveBeenCalledWith(sql, {}, expect.any(Function))
|
expect(config.integration.client.query).toHaveBeenCalledWith(sql, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls the delete method with the correct params", async () => {
|
it("calls the delete method with the correct params", async () => {
|
||||||
|
@ -43,7 +43,7 @@ describe("MySQL Integration", () => {
|
||||||
await config.integration.delete({
|
await config.integration.delete({
|
||||||
sql
|
sql
|
||||||
})
|
})
|
||||||
expect(config.integration.client.query).toHaveBeenCalledWith(sql, {}, expect.any(Function))
|
expect(config.integration.client.query).toHaveBeenCalledWith(sql, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("no rows returned", () => {
|
describe("no rows returned", () => {
|
||||||
|
|
|
@ -40,7 +40,7 @@ const SQL_TYPE_MAP = {
|
||||||
export enum SqlClients {
|
export enum SqlClients {
|
||||||
MS_SQL = "mssql",
|
MS_SQL = "mssql",
|
||||||
POSTGRES = "pg",
|
POSTGRES = "pg",
|
||||||
MY_SQL = "mysql",
|
MY_SQL = "mysql2",
|
||||||
ORACLE = "oracledb",
|
ORACLE = "oracledb",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue