Adding a mySQL plus integration, as well as fixing some issues with running queries directly.

This commit is contained in:
mike12345567 2021-06-18 12:29:25 +01:00
parent a04c930c1e
commit 7065bf1ea9
7 changed files with 156 additions and 20 deletions

View File

@ -100,16 +100,18 @@
having to write any queries at all. having to write any queries at all.
</Body> </Body>
<div class="query-list"> <div class="query-list">
{#each Object.keys(datasource.entities) as entity} {#if datasource.entities}
<div {#each Object.keys(datasource.entities) as entity}
class="query-list-item" <div
on:click={() => onClickTable(datasource.entities[entity])} class="query-list-item"
> on:click={() => onClickTable(datasource.entities[entity])}
<p class="query-name">{entity}</p> >
<p>Primary Key: {datasource.entities[entity].primary}</p> <p class="query-name">{entity}</p>
<p></p> <p>Primary Key: {datasource.entities[entity].primary}</p>
</div> <p></p>
{/each} </div>
{/each}
{/if}
</div> </div>
{/if} {/if}
<Divider /> <Divider />

View File

@ -0,0 +1,21 @@
# Use root/example as user/password credentials
version: '3.1'
services:
db:
image: mysql
restart: always
command: --init-file /data/application/init.sql --default-authentication-plugin=mysql_native_password
volumes:
- ./init.sql:/data/application/init.sql
environment:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
adminer:
image: adminer
restart: always
ports:
- 8080:8080

View File

@ -0,0 +1,9 @@
CREATE DATABASE IF NOT EXISTS main;
USE main;
CREATE TABLE Persons (
PersonID int NOT NULL PRIMARY KEY,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
);

View File

@ -51,9 +51,11 @@ const SCHEMA = {
}, },
} }
async function internalQuery(client, sql) { async function internalQuery(client, query) {
const sql = typeof query === "string" ? query : query.sql
const bindings = typeof query === "string" ? {} : query.bindings
try { try {
return await client.query(sql.sql, sql.bindings) return await client.query(sql, bindings)
} catch (err) { } catch (err) {
throw new Error(err) throw new Error(err)
} }

View File

@ -1,9 +1,35 @@
const mysql = require("mysql") const mysql = require("mysql")
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") const { FIELD_TYPES, QUERY_TYPES } = require("./Integration")
const Sql = require("./base/sql") const Sql = require("./base/sql")
const { buildExternalTableId, convertType } = require("./utils")
const { FieldTypes } = require("../constants")
const TYPE_MAP = {
text: FieldTypes.LONGFORM,
blob: FieldTypes.LONGFORM,
enum: FieldTypes.STRING,
varchar: FieldTypes.STRING,
int: FieldTypes.NUMBER,
numeric: FieldTypes.NUMBER,
bigint: FieldTypes.NUMBER,
mediumint: FieldTypes.NUMBER,
decimal: FieldTypes.NUMBER,
dec: FieldTypes.NUMBER,
double: FieldTypes.NUMBER,
real: FieldTypes.NUMBER,
fixed: FieldTypes.NUMBER,
smallint: FieldTypes.NUMBER,
timestamp: FieldTypes.DATETIME,
date: FieldTypes.DATETIME,
datetime: FieldTypes.DATETIME,
time: FieldTypes.DATETIME,
tinyint: FieldTypes.BOOLEAN,
json: FIELD_TYPES.JSON,
}
const SCHEMA = { const SCHEMA = {
docs: "https://github.com/mysqljs/mysql", docs: "https://github.com/mysqljs/mysql",
plus: true,
friendlyName: "MySQL", friendlyName: "MySQL",
description: description:
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ", "MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
@ -53,15 +79,21 @@ const SCHEMA = {
}, },
} }
function internalQuery(client, query) { function internalQuery(client, query, connect = true) {
const sql = typeof query === "string" ? query : query.sql
const bindings = typeof query === "string" ? {} : query.bindings
// Node MySQL is callback based, so we must wrap our call in a promise // Node MySQL is callback based, so we must wrap our call in a promise
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
client.connect() if (connect) {
return client.query(query.sql, query.bindings, (error, results) => { client.connect()
}
return client.query(sql, bindings, (error, results) => {
if (error) { if (error) {
reject(error) reject(error)
} else { } else {
resolve(results) resolve(results)
}
if (connect) {
client.end() client.end()
} }
}) })
@ -69,15 +101,73 @@ function internalQuery(client, query) {
} }
class MySQLIntegration extends Sql { class MySQLIntegration extends Sql {
GET_TABLES_SQL =
"select * from information_schema.columns where table_schema = 'public'"
PRIMARY_KEYS_SQL = `
select tc.table_schema, tc.table_name, kc.column_name as primary_key
from information_schema.table_constraints tc
join
information_schema.key_column_usage kc on kc.table_name = tc.table_name
and kc.table_schema = tc.table_schema
and kc.constraint_name = tc.constraint_name
where tc.constraint_type = 'PRIMARY KEY';
`
constructor(config) { constructor(config) {
super("mysql") super("mysql")
this.config = config this.config = config
if (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.client = mysql.createConnection(config)
} }
async buildSchema(datasourceId) {
const tables = {}
const database = this.config.database
this.client.connect()
// get the tables first
const tablesResp = await internalQuery(this.client, "SHOW TABLES;", false)
const tableNames = tablesResp.map(obj => obj[`Tables_in_${database}`])
for (let tableName of tableNames) {
const primaryKeys = []
const schema = {}
const descResp = await internalQuery(this.client, `DESCRIBE ${tableName};`, false)
for (let column of descResp) {
const columnName = column.Field
if (column.Key === "PRI") {
primaryKeys.push(columnName)
}
const constraints = {}
if (column.Null !== "YES") {
constraints.required = true
}
schema[columnName] = {
name: columnName,
type: convertType(column.Type, TYPE_MAP),
constraints,
}
}
// for now just default to first column
if (primaryKeys.length === 0) {
primaryKeys.push(descResp[0].Field)
}
if (!tables[tableName]) {
tables[tableName] = {
_id: buildExternalTableId(datasourceId, tableName),
primary: primaryKeys,
name: tableName,
schema,
}
}
}
this.client.end()
this.tables = tables
}
async create(query) { async create(query) {
const results = await internalQuery(this.client, query) const results = await internalQuery(this.client, query)
return results.length ? results : [{ created: true }] return results.length ? results : [{ created: true }]

View File

@ -2,7 +2,7 @@ const { Pool } = require("pg")
const { FIELD_TYPES } = require("./Integration") const { FIELD_TYPES } = require("./Integration")
const Sql = require("./base/sql") const Sql = require("./base/sql")
const { FieldTypes } = require("../constants") const { FieldTypes } = require("../constants")
const { buildExternalTableId } = require("./utils") const { buildExternalTableId, convertType } = require("./utils")
const SCHEMA = { const SCHEMA = {
docs: "https://node-postgres.com", docs: "https://node-postgres.com",
@ -71,9 +71,11 @@ const TYPE_MAP = {
json: FIELD_TYPES.JSON, json: FIELD_TYPES.JSON,
} }
async function internalQuery(client, sql) { async function internalQuery(client, query) {
const sql = typeof query === "string" ? query : query.sql
const bindings = typeof query === "string" ? {} : query.bindings
try { try {
return await client.query(sql.sql, sql.bindings) return await client.query(sql, bindings)
} catch (err) { } catch (err) {
throw new Error(err) throw new Error(err)
} }
@ -147,7 +149,7 @@ class PostgresIntegration extends Sql {
tables[tableName].schema[columnName] = { tables[tableName].schema[columnName] = {
name: columnName, name: columnName,
type: TYPE_MAP[column.data_type] || FIELD_TYPES.STRING, type: convertType(column.data_type, TYPE_MAP),
} }
} }
this.tables = tables this.tables = tables

View File

@ -1,4 +1,5 @@
const { DocumentTypes, SEPARATOR } = require("../db/utils") const { DocumentTypes, SEPARATOR } = require("../db/utils")
const { FieldTypes } = require("../constants")
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
@ -33,3 +34,12 @@ exports.breakRowIdField = _id => {
} }
return JSON.parse(decodeURIComponent(_id)) return JSON.parse(decodeURIComponent(_id))
} }
exports.convertType = (type, map) => {
for (let [external, internal] of Object.entries(map)) {
if (type.toLowerCase().includes(external)) {
return internal
}
}
return FieldTypes.STRING
}