2021-06-24 19:17:26 +02:00
|
|
|
import {
|
|
|
|
Integration,
|
|
|
|
DatasourceFieldTypes,
|
|
|
|
QueryTypes,
|
|
|
|
QueryJson,
|
2021-06-25 14:46:02 +02:00
|
|
|
SqlQuery,
|
2021-06-27 00:09:46 +02:00
|
|
|
} from "../definitions/datasource"
|
2021-06-28 11:21:37 +02:00
|
|
|
import { Table, TableSchema } from "../definitions/common"
|
2021-11-08 19:12:40 +01:00
|
|
|
import {
|
|
|
|
getSqlQuery,
|
|
|
|
SqlClients,
|
|
|
|
buildExternalTableId,
|
|
|
|
convertSqlType,
|
|
|
|
finaliseExternalTables,
|
|
|
|
} from "./utils"
|
2021-10-26 21:03:54 +02:00
|
|
|
import { DatasourcePlus } from "./base/datasourcePlus"
|
2021-06-24 19:16:48 +02:00
|
|
|
|
|
|
|
module MySQLModule {
|
2022-03-03 20:20:26 +01:00
|
|
|
const mysql = require("mysql2/promise")
|
2021-06-24 19:16:48 +02:00
|
|
|
const Sql = require("./base/sql")
|
|
|
|
|
|
|
|
interface MySQLConfig {
|
|
|
|
host: string
|
|
|
|
port: number
|
|
|
|
user: string
|
|
|
|
password: string
|
|
|
|
database: string
|
|
|
|
ssl?: object
|
|
|
|
}
|
|
|
|
|
|
|
|
const SCHEMA: Integration = {
|
2022-03-03 20:20:26 +01:00
|
|
|
docs: "https://github.com/sidorares/node-mysql2",
|
2021-06-24 19:16:48 +02:00
|
|
|
plus: true,
|
|
|
|
friendlyName: "MySQL",
|
|
|
|
description:
|
|
|
|
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
|
|
|
|
datasource: {
|
|
|
|
host: {
|
|
|
|
type: DatasourceFieldTypes.STRING,
|
|
|
|
default: "localhost",
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
port: {
|
|
|
|
type: DatasourceFieldTypes.NUMBER,
|
|
|
|
default: 3306,
|
|
|
|
required: false,
|
|
|
|
},
|
|
|
|
user: {
|
|
|
|
type: DatasourceFieldTypes.STRING,
|
|
|
|
default: "root",
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
password: {
|
|
|
|
type: DatasourceFieldTypes.PASSWORD,
|
|
|
|
default: "root",
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
database: {
|
|
|
|
type: DatasourceFieldTypes.STRING,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
ssl: {
|
|
|
|
type: DatasourceFieldTypes.OBJECT,
|
|
|
|
required: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
query: {
|
|
|
|
create: {
|
|
|
|
type: QueryTypes.SQL,
|
|
|
|
},
|
|
|
|
read: {
|
|
|
|
type: QueryTypes.SQL,
|
|
|
|
},
|
|
|
|
update: {
|
|
|
|
type: QueryTypes.SQL,
|
|
|
|
},
|
|
|
|
delete: {
|
|
|
|
type: QueryTypes.SQL,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-03-30 15:31:17 +02:00
|
|
|
function bindingTypeCoerce(bindings: any[]) {
|
|
|
|
for (let i = 0; i < bindings.length; i++) {
|
|
|
|
const binding = bindings[i]
|
|
|
|
if (typeof binding !== "string") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const matches = binding.match(/^\d*/g)
|
|
|
|
if (matches && matches[0] !== "" && !isNaN(Number(matches[0]))) {
|
|
|
|
bindings[i] = parseFloat(binding)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return bindings
|
|
|
|
}
|
|
|
|
|
2021-10-26 21:03:54 +02:00
|
|
|
class MySQLIntegration extends Sql implements DatasourcePlus {
|
2021-06-24 19:16:48 +02:00
|
|
|
private config: MySQLConfig
|
2022-03-03 20:20:26 +01:00
|
|
|
private client: any
|
2021-10-26 21:03:54 +02:00
|
|
|
public tables: Record<string, Table> = {}
|
|
|
|
public schemaErrors: Record<string, string> = {}
|
2021-06-24 19:16:48 +02:00
|
|
|
|
|
|
|
constructor(config: MySQLConfig) {
|
2021-11-08 19:12:40 +01:00
|
|
|
super(SqlClients.MY_SQL)
|
2021-06-24 19:16:48 +02:00
|
|
|
this.config = config
|
|
|
|
if (config.ssl && Object.keys(config.ssl).length === 0) {
|
|
|
|
delete config.ssl
|
|
|
|
}
|
2022-03-03 20:20:26 +01:00
|
|
|
this.config = config
|
2021-06-24 19:16:48 +02:00
|
|
|
}
|
|
|
|
|
2022-03-02 23:45:10 +01:00
|
|
|
getBindingIdentifier(): string {
|
|
|
|
return "?"
|
|
|
|
}
|
|
|
|
|
2022-03-11 01:19:26 +01:00
|
|
|
getStringConcat(parts: string[]): string {
|
|
|
|
return `concat(${parts.join(", ")})`
|
|
|
|
}
|
|
|
|
|
2022-03-03 20:20:26 +01:00
|
|
|
async connect() {
|
|
|
|
this.client = await mysql.createConnection(this.config)
|
|
|
|
}
|
|
|
|
|
|
|
|
async disconnect() {
|
|
|
|
await this.client.end()
|
|
|
|
}
|
|
|
|
|
|
|
|
async internalQuery(
|
2022-03-02 23:45:10 +01:00
|
|
|
query: SqlQuery,
|
|
|
|
connect: boolean = true
|
|
|
|
): Promise<any[] | any> {
|
2022-03-03 20:20:26 +01:00
|
|
|
try {
|
2022-03-02 23:45:10 +01:00
|
|
|
if (connect) {
|
2022-03-03 20:20:26 +01:00
|
|
|
await this.connect()
|
2022-03-02 23:45:10 +01:00
|
|
|
}
|
2022-03-03 20:20:26 +01:00
|
|
|
// Node MySQL is callback based, so we must wrap our call in a promise
|
|
|
|
const response = await this.client.query(
|
2022-03-02 23:45:10 +01:00
|
|
|
query.sql,
|
2022-03-30 15:31:17 +02:00
|
|
|
bindingTypeCoerce(query.bindings || [])
|
2022-03-02 23:45:10 +01:00
|
|
|
)
|
2022-03-03 20:20:26 +01:00
|
|
|
return response[0]
|
|
|
|
} finally {
|
|
|
|
if (connect) {
|
|
|
|
await this.disconnect()
|
|
|
|
}
|
|
|
|
}
|
2022-03-02 23:45:10 +01:00
|
|
|
}
|
|
|
|
|
2021-08-31 15:44:33 +02:00
|
|
|
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
2021-06-27 00:09:46 +02:00
|
|
|
const tables: { [key: string]: Table } = {}
|
2021-06-24 19:16:48 +02:00
|
|
|
const database = this.config.database
|
2022-03-03 20:20:26 +01:00
|
|
|
await this.connect()
|
|
|
|
|
|
|
|
try {
|
|
|
|
// get the tables first
|
|
|
|
const tablesResp = await this.internalQuery(
|
|
|
|
{ sql: "SHOW TABLES;" },
|
2021-06-24 19:16:48 +02:00
|
|
|
false
|
|
|
|
)
|
2022-03-03 20:20:26 +01:00
|
|
|
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 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,
|
|
|
|
}
|
2021-06-24 19:16:48 +02:00
|
|
|
}
|
2022-03-03 20:20:26 +01:00
|
|
|
if (!tables[tableName]) {
|
|
|
|
tables[tableName] = {
|
|
|
|
_id: buildExternalTableId(datasourceId, tableName),
|
|
|
|
primary: primaryKeys,
|
|
|
|
name: tableName,
|
|
|
|
schema,
|
|
|
|
}
|
2021-06-24 19:16:48 +02:00
|
|
|
}
|
|
|
|
}
|
2022-03-03 20:20:26 +01:00
|
|
|
} finally {
|
|
|
|
await this.disconnect()
|
2021-06-24 19:16:48 +02:00
|
|
|
}
|
2021-10-26 21:03:54 +02:00
|
|
|
const final = finaliseExternalTables(tables, entities)
|
|
|
|
this.tables = final.tables
|
|
|
|
this.schemaErrors = final.errors
|
2021-06-24 19:16:48 +02:00
|
|
|
}
|
|
|
|
|
2021-06-25 14:46:02 +02:00
|
|
|
async create(query: SqlQuery | string) {
|
2022-03-02 23:45:10 +01:00
|
|
|
const results = await this.internalQuery(getSqlQuery(query))
|
2021-06-24 19:16:48 +02:00
|
|
|
return results.length ? results : [{ created: true }]
|
|
|
|
}
|
|
|
|
|
2021-11-10 20:35:09 +01:00
|
|
|
async read(query: SqlQuery | string) {
|
2022-03-02 23:45:10 +01:00
|
|
|
return this.internalQuery(getSqlQuery(query))
|
2021-06-24 19:16:48 +02:00
|
|
|
}
|
|
|
|
|
2021-06-25 14:46:02 +02:00
|
|
|
async update(query: SqlQuery | string) {
|
2022-03-02 23:45:10 +01:00
|
|
|
const results = await this.internalQuery(getSqlQuery(query))
|
2021-06-24 19:16:48 +02:00
|
|
|
return results.length ? results : [{ updated: true }]
|
|
|
|
}
|
|
|
|
|
2021-06-25 14:46:02 +02:00
|
|
|
async delete(query: SqlQuery | string) {
|
2022-03-02 23:45:10 +01:00
|
|
|
const results = await this.internalQuery(getSqlQuery(query))
|
2021-06-24 19:16:48 +02:00
|
|
|
return results.length ? results : [{ deleted: true }]
|
|
|
|
}
|
|
|
|
|
|
|
|
async query(json: QueryJson) {
|
2022-03-03 20:20:26 +01:00
|
|
|
await this.connect()
|
|
|
|
try {
|
|
|
|
const queryFn = (query: any) => this.internalQuery(query, false)
|
|
|
|
return await this.queryWithReturning(json, queryFn)
|
|
|
|
} finally {
|
|
|
|
await this.disconnect()
|
|
|
|
}
|
2021-06-24 19:16:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
schema: SCHEMA,
|
|
|
|
integration: MySQLIntegration,
|
|
|
|
}
|
|
|
|
}
|