diff --git a/package.json b/package.json index 5ecf10b7..3d7e3f8b 100644 --- a/package.json +++ b/package.json @@ -51,9 +51,9 @@ "electron-log": "^4.2.4", "electron-updater": "^4.3.5", "lodash": "^4.17.20", - "moment": "^2.27.0", + "moment": "^2.29.0", "monaco-editor": "^0.20.0", - "mssql": "^6.2.1", + "mssql": "^6.2.2", "mysql": "^2.18.1", "pg": "^8.3.3", "source-map-support": "^0.5.16", @@ -80,7 +80,7 @@ "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", "eslint-plugin-vue": "^6.2.2", - "monaco-editor-webpack-plugin": "^1.9.0", + "monaco-editor-webpack-plugin": "^1.9.1", "node-sass": "^4.14.1", "sass-loader": "^10.0.2", "standard-version": "^9.0.0", @@ -89,6 +89,6 @@ "stylelint-scss": "^3.18.0", "vue": "^2.6.12", "vue-template-compiler": "^2.6.12", - "webpack": "^4.44.1" + "webpack": "^4.44.2" } } diff --git a/src/main/ipc-handlers/connection.js b/src/main/ipc-handlers/connection.js index 91b2f95e..a65acd83 100644 --- a/src/main/ipc-handlers/connection.js +++ b/src/main/ipc-handlers/connection.js @@ -1,12 +1,10 @@ import { ipcMain } from 'electron'; import { ClientsFactory } from '../libs/ClientsFactory'; -import InformationSchema from '../models/InformationSchema'; -import Generic from '../models/Generic'; export default connections => { ipcMain.handle('test-connection', async (event, conn) => { - const Connection = ClientsFactory.getConnection({ + const connection = ClientsFactory.getConnection({ client: conn.client, params: { host: conn.host, @@ -16,11 +14,11 @@ export default connections => { } }); - await Connection.connect(); + await connection.connect(); try { - await InformationSchema.testConnection(Connection); - Connection.destroy(); + await connection.select('1+1').run(); + connection.destroy(); return { status: 'success' }; } @@ -34,7 +32,7 @@ export default connections => { }); ipcMain.handle('connect', async (event, conn) => { - const Connection = ClientsFactory.getConnection({ + const connection = ClientsFactory.getConnection({ client: conn.client, params: { host: conn.host, @@ -46,10 +44,16 @@ export default connections => { }); try { - await Connection.connect(); + await connection.connect(); - const { rows: structure } = await InformationSchema.getStructure(Connection); - connections[conn.uid] = Connection; + const { rows: structure } = await connection + .select('*') + .schema('information_schema') + .from('TABLES') + .orderBy({ TABLE_SCHEMA: 'ASC', TABLE_NAME: 'ASC' }) + .run(); + + connections[conn.uid] = connection; return { status: 'success', response: structure }; } @@ -63,9 +67,15 @@ export default connections => { delete connections[uid]; }); - ipcMain.handle('refresh', async (event, uid) => { + ipcMain.handle('get-structure', async (event, uid) => { try { - const { rows: structure } = await InformationSchema.getStructure(connections[uid]); + const { rows: structure } = await connections[uid] + .select('*') + .schema('information_schema') + .from('TABLES') + .orderBy({ TABLE_SCHEMA: 'ASC', TABLE_NAME: 'ASC' }) + .run(); + return { status: 'success', response: structure }; } catch (err) { @@ -75,8 +85,13 @@ export default connections => { ipcMain.handle('raw-query', async (event, { uid, query, schema }) => { if (!query) return; + try { - const result = await Generic.raw(connections[uid], query, schema); + if (schema) + await connections[uid].use(schema); + + const result = await connections[uid].raw(query, true); + return { status: 'success', response: result }; } catch (err) { diff --git a/src/main/ipc-handlers/index.js b/src/main/ipc-handlers/index.js index ebe5e0d7..dc80d3ad 100644 --- a/src/main/ipc-handlers/index.js +++ b/src/main/ipc-handlers/index.js @@ -2,12 +2,14 @@ import connection from './connection'; import tables from './tables'; import updates from './updates'; import application from './application'; +import properties from './properties'; const connections = {}; export default () => { connection(connections); tables(connections); + properties(connections); updates(); application(); }; diff --git a/src/main/ipc-handlers/properties.js b/src/main/ipc-handlers/properties.js new file mode 100644 index 00000000..5067c4a4 --- /dev/null +++ b/src/main/ipc-handlers/properties.js @@ -0,0 +1,14 @@ +import { ipcMain } from 'electron'; + +export default (connections) => { + ipcMain.handle('get-collations', async (event, uid) => { + try { + const result = await connections[uid].getCollations(); + + return { status: 'success', response: result }; + } + catch (err) { + return { status: 'error', response: err.toString() }; + } + }); +}; diff --git a/src/main/ipc-handlers/tables.js b/src/main/ipc-handlers/tables.js index 7ac9d5f8..79cfa039 100644 --- a/src/main/ipc-handlers/tables.js +++ b/src/main/ipc-handlers/tables.js @@ -1,13 +1,39 @@ import { ipcMain } from 'electron'; -import InformationSchema from '../models/InformationSchema'; -import Tables from '../models/Tables'; +import { sqlEscaper } from 'common/libs/sqlEscaper'; +import { TEXT, LONG_TEXT, NUMBER, BLOB } from 'common/fieldTypes'; +import fs from 'fs'; // TODO: remap objects based on client export default (connections) => { ipcMain.handle('get-table-columns', async (event, { uid, schema, table }) => { try { - const result = await InformationSchema.getTableColumns(connections[uid], schema, table);// TODO: uniform column properties + const { rows } = await connections[uid] + .select('*') + .schema('information_schema') + .from('COLUMNS') + .where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` }) + .orderBy({ ORDINAL_POSITION: 'ASC' }) + .run(); + + const result = rows.map(field => { + return { + name: field.COLUMN_NAME, + key: field.COLUMN_KEY.toLowerCase(), + type: field.DATA_TYPE, + schema: field.TABLE_SCHEMA, + table: field.TABLE_NAME, + numPrecision: field.NUMERIC_PRECISION, + datePrecision: field.DATETIME_PRECISION, + charLength: field.CHARACTER_MAXIMUM_LENGTH, + isNullable: field.IS_NULLABLE, + default: field.COLUMN_DEFAULT, + charset: field.CHARACTER_SET_NAME, + collation: field.COLLATION_NAME, + autoIncrement: field.EXTRA.includes('auto_increment'), + comment: field.COLUMN_COMMENT + }; + }); return { status: 'success', response: result }; } catch (err) { @@ -17,7 +43,13 @@ export default (connections) => { ipcMain.handle('get-table-data', async (event, { uid, schema, table }) => { try { - const result = await Tables.getTableData(connections[uid], schema, table); + const result = await connections[uid] + .select('*') + .schema(schema) + .from(table) + .limit(1000) + .run(); + return { status: 'success', response: result }; } catch (err) { @@ -27,7 +59,27 @@ export default (connections) => { ipcMain.handle('get-key-usage', async (event, { uid, schema, table }) => { try { - const result = await InformationSchema.getKeyUsage(connections[uid], schema, table); + const { rows } = await connections[uid] + .select('*') + .schema('information_schema') + .from('KEY_COLUMN_USAGE') + .where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' }) + .run(); + + const result = rows.map(field => { + return { + schema: field.TABLE_SCHEMA, + table: field.TABLE_NAME, + column: field.COLUMN_NAME, + position: field.ORDINAL_POSITION, + constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT, + constraintName: field.CONSTRAINT_NAME, + refSchema: field.REFERENCED_TABLE_SCHEMA, + refTable: field.REFERENCED_TABLE_NAME, + refColumn: field.REFERENCED_COLUMN_NAME + }; + }); + return { status: 'success', response: result }; } catch (err) { @@ -37,8 +89,34 @@ export default (connections) => { ipcMain.handle('update-table-cell', async (event, params) => { try { - const result = await Tables.updateTableCell(connections[params.uid], params); - return { status: 'success', response: result }; + let escapedParam; + let reload = false; + const id = typeof params.id === 'number' ? params.id : `"${params.id}"`; + + if (NUMBER.includes(params.type)) + escapedParam = params.content; + else if ([...TEXT, ...LONG_TEXT].includes(params.type)) + escapedParam = `"${sqlEscaper(params.content)}"`; + else if (BLOB.includes(params.type)) { + if (params.content) { + const fileBlob = fs.readFileSync(params.content); + escapedParam = `0x${fileBlob.toString('hex')}`; + reload = true; + } + else + escapedParam = '""'; + } + else + escapedParam = `"${sqlEscaper(params.content)}"`; + + await connections[params.uid] + .update({ [params.field]: `= ${escapedParam}` }) + .schema(params.schema) + .from(params.table) + .where({ [params.primary]: `= ${id}` }) + .run(); + + return { status: 'success', response: { reload } }; } catch (err) { return { status: 'error', response: err.toString() }; @@ -47,7 +125,12 @@ export default (connections) => { ipcMain.handle('delete-table-rows', async (event, params) => { try { - const result = await Tables.deleteTableRows(connections[params.uid], params); + const result = await connections[params.uid] + .schema(params.schema) + .delete(params.table) + .where({ [params.primary]: `IN (${params.rows.join(',')})` }) + .run(); + return { status: 'success', response: result }; } catch (err) { @@ -57,7 +140,39 @@ export default (connections) => { ipcMain.handle('insert-table-rows', async (event, params) => { try { - await Tables.insertTableRows(connections[params.uid], params); + const insertObj = {}; + for (const key in params.row) { + const type = params.fields[key]; + let escapedParam; + + if (params.row[key] === null) + escapedParam = 'NULL'; + else if (NUMBER.includes(type)) + escapedParam = params.row[key]; + else if ([...TEXT, ...LONG_TEXT].includes(type)) + escapedParam = `"${sqlEscaper(params.row[key])}"`; + else if (BLOB.includes(type)) { + if (params.row[key]) { + const fileBlob = fs.readFileSync(params.row[key]); + escapedParam = `0x${fileBlob.toString('hex')}`; + } + else + escapedParam = '""'; + } + else + escapedParam = `"${sqlEscaper(params.row[key])}"`; + + insertObj[key] = escapedParam; + } + + for (let i = 0; i < params.repeat; i++) { + await connections[params.uid] + .schema(params.schema) + .into(params.table) + .insert(insertObj) + .run(); + } + return { status: 'success' }; } catch (err) { @@ -67,7 +182,17 @@ export default (connections) => { ipcMain.handle('get-foreign-list', async (event, params) => { try { - const results = await Tables.getForeignList(connections[params.uid], params); + const query = connections[params.uid] + .select(`${params.column} AS foreignColumn`) + .schema(params.schema) + .from(params.table) + .orderBy('foreignColumn ASC'); + + if (params.description) + query.select(`LEFT(${params.description}, 20) AS foreignDescription`); + + const results = await query.run(); + return { status: 'success', response: results }; } catch (err) { diff --git a/src/main/libs/clients/MySQLClient.js b/src/main/libs/clients/MySQLClient.js index d8fbcc74..a128dbb3 100644 --- a/src/main/libs/clients/MySQLClient.js +++ b/src/main/libs/clients/MySQLClient.js @@ -31,6 +31,11 @@ export class MySQLClient extends AntaresCore { return this.raw(sql); } + getCollations () { + const sql = 'SHOW COLLATION'; + return this.raw(sql); + } + /** * @returns {string} SQL string * @memberof MySQLClient diff --git a/src/main/models/Generic.js b/src/main/models/Generic.js deleted file mode 100644 index cee163d4..00000000 --- a/src/main/models/Generic.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; -export default class { - static async raw (connection, query, schema) { - if (schema) { - try { - await connection.use(schema); - } - catch (err) { - return err; - } - } - return connection.raw(query, true); - } -} diff --git a/src/main/models/InformationSchema.js b/src/main/models/InformationSchema.js deleted file mode 100644 index fa19b76f..00000000 --- a/src/main/models/InformationSchema.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; -export default class { - static testConnection (connection) { - return connection.select('1+1').run(); - } - - static getStructure (connection) { - return connection - .select('*') - .schema('information_schema') - .from('TABLES') - .orderBy({ TABLE_SCHEMA: 'ASC', TABLE_NAME: 'ASC' }) - .run(); - } - - static async getTableColumns (connection, schema, table) { - const { rows } = await connection - .select('*') - .schema('information_schema') - .from('COLUMNS') - .where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` }) - .orderBy({ ORDINAL_POSITION: 'ASC' }) - .run(); - - return rows.map(field => { - return { - name: field.COLUMN_NAME, - key: field.COLUMN_KEY.toLowerCase(), - type: field.DATA_TYPE, - schema: field.TABLE_SCHEMA, - table: field.TABLE_NAME, - numPrecision: field.NUMERIC_PRECISION, - datePrecision: field.DATETIME_PRECISION, - charLength: field.CHARACTER_MAXIMUM_LENGTH, - isNullable: field.IS_NULLABLE, - default: field.COLUMN_DEFAULT, - charset: field.CHARACTER_SET_NAME, - collation: field.COLLATION_NAME, - autoIncrement: field.EXTRA.includes('auto_increment'), - comment: field.COLUMN_COMMENT - }; - }); - } - - static async getKeyUsage (connection, schema, table) { - const { rows } = await connection - .select('*') - .schema('information_schema') - .from('KEY_COLUMN_USAGE') - .where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' }) - .run(); - - return rows.map(field => { - return { - schema: field.TABLE_SCHEMA, - table: field.TABLE_NAME, - column: field.COLUMN_NAME, - position: field.ORDINAL_POSITION, - constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT, - constraintName: field.CONSTRAINT_NAME, - refSchema: field.REFERENCED_TABLE_SCHEMA, - refTable: field.REFERENCED_TABLE_NAME, - refColumn: field.REFERENCED_COLUMN_NAME - }; - }); - } -} diff --git a/src/main/models/Tables.js b/src/main/models/Tables.js deleted file mode 100644 index 046b2de2..00000000 --- a/src/main/models/Tables.js +++ /dev/null @@ -1,102 +0,0 @@ -'use strict'; -import { sqlEscaper } from 'common/libs/sqlEscaper'; -import { TEXT, LONG_TEXT, NUMBER, BLOB } from 'common/fieldTypes'; -import fs from 'fs'; - -export default class { - static async getTableData (connection, schema, table) { - return connection - .select('*') - .schema(schema) - .from(table) - .limit(1000) - .run(); - } - - static async updateTableCell (connection, params) { - let escapedParam; - let reload = false; - const id = typeof params.id === 'number' ? params.id : `"${params.id}"`; - - if (NUMBER.includes(params.type)) - escapedParam = params.content; - else if ([...TEXT, ...LONG_TEXT].includes(params.type)) - escapedParam = `"${sqlEscaper(params.content)}"`; - else if (BLOB.includes(params.type)) { - if (params.content) { - const fileBlob = fs.readFileSync(params.content); - escapedParam = `0x${fileBlob.toString('hex')}`; - reload = true; - } - else - escapedParam = '""'; - } - else - escapedParam = `"${sqlEscaper(params.content)}"`; - - await connection - .update({ [params.field]: `= ${escapedParam}` }) - .schema(params.schema) - .from(params.table) - .where({ [params.primary]: `= ${id}` }) - .run(); - - return { reload }; - } - - static async deleteTableRows (connection, params) { - return connection - .schema(params.schema) - .delete(params.table) - .where({ [params.primary]: `IN (${params.rows.join(',')})` }) - .run(); - } - - static async insertTableRows (connection, params) { - const insertObj = {}; - for (const key in params.row) { - const type = params.fields[key]; - let escapedParam; - - if (params.row[key] === null) - escapedParam = 'NULL'; - else if (NUMBER.includes(type)) - escapedParam = params.row[key]; - else if ([...TEXT, ...LONG_TEXT].includes(type)) - escapedParam = `"${sqlEscaper(params.row[key])}"`; - else if (BLOB.includes(type)) { - if (params.row[key]) { - const fileBlob = fs.readFileSync(params.row[key]); - escapedParam = `0x${fileBlob.toString('hex')}`; - } - else - escapedParam = '""'; - } - else - escapedParam = `"${sqlEscaper(params.row[key])}"`; - - insertObj[key] = escapedParam; - } - - for (let i = 0; i < params.repeat; i++) { - await connection - .schema(params.schema) - .into(params.table) - .insert(insertObj) - .run(); - } - } - - static async getForeignList (connection, params) { - const query = connection - .select(`${params.column} AS foreignColumn`) - .schema(params.schema) - .from(params.table) - .orderBy('foreignColumn ASC'); - - if (params.description) - query.select(`LEFT(${params.description}, 20) AS foreignDescription`); - - return query.run(); - } -} diff --git a/src/renderer/components/ModalAskCredentials.vue b/src/renderer/components/ModalAskCredentials.vue index f57a4250..0e57c26b 100644 --- a/src/renderer/components/ModalAskCredentials.vue +++ b/src/renderer/components/ModalAskCredentials.vue @@ -10,7 +10,7 @@ -