diff --git a/api/src/controllers/databaseConnections.js b/api/src/controllers/databaseConnections.js index 875e193bb..f0bcea063 100644 --- a/api/src/controllers/databaseConnections.js +++ b/api/src/controllers/databaseConnections.js @@ -13,11 +13,13 @@ module.exports = { handle_structure(conid, database, { structure }) { const existing = this.opened.find(x => x.conid == conid && x.database == database); if (!existing) return; - existing.structure = structure;conid + existing.structure = structure; + conid; socket.emit(`database-structure-changed-${conid}-${database}`); }, - handle_error(conid, { error }) { - console.log(error); + handle_error(conid, database, props) { + const { error } = props; + console.log(`Error in database connection ${conid}, database ${database}: ${error}`); }, handle_response(conid, database, { msgid, ...response }) { const [resolve, reject] = this.requests[msgid]; diff --git a/api/src/controllers/serverConnections.js b/api/src/controllers/serverConnections.js index 6b7c64439..f7fc10627 100644 --- a/api/src/controllers/serverConnections.js +++ b/api/src/controllers/serverConnections.js @@ -13,7 +13,7 @@ module.exports = { socket.emit(`database-list-changed-${conid}`); }, handle_error(conid, { error }) { - console.log(error); + console.log(`Error in server connection ${conid}: ${error}`); }, async ensureOpened(conid) { diff --git a/api/src/controllers/tables.js b/api/src/controllers/tables.js index 6bf939eab..ebcb169a5 100644 --- a/api/src/controllers/tables.js +++ b/api/src/controllers/tables.js @@ -3,9 +3,9 @@ const databaseConnections = require('./databaseConnections'); module.exports = { tableData_meta: 'get', - async tableData({ id, database, schemaName, pureName }) { - const opened = await databaseConnections.ensureOpened(id, database); - // const res = opened.sendRequest({ msgtype: 'tableData', schemaName, pureName }); - // return res; + async tableData({ conid, database, schemaName, pureName }) { + const opened = await databaseConnections.ensureOpened(conid, database); + const res = await databaseConnections.sendRequest(opened, { msgtype: 'tableData', schemaName, pureName }); + return res; }, }; diff --git a/api/src/engines/mssql/MsSqlAnalyser.js b/api/src/engines/mssql/MsSqlAnalyser.js index bc09e01c3..941a89d9c 100644 --- a/api/src/engines/mssql/MsSqlAnalyser.js +++ b/api/src/engines/mssql/MsSqlAnalyser.js @@ -33,7 +33,7 @@ class MsSqlAnalyser extends DatabaseAnalayser { // name: table.tableName, // }; // } - this.result.tables = tables; + this.result.tables = tables.rows; } } diff --git a/api/src/engines/mssql/index.js b/api/src/engines/mssql/index.js index 38571f02f..55bffbb84 100644 --- a/api/src/engines/mssql/index.js +++ b/api/src/engines/mssql/index.js @@ -1,3 +1,4 @@ +const _ = require('lodash'); const mssql = require('mssql'); const MsSqlAnalyser = require('./MsSqlAnalyser'); @@ -8,15 +9,17 @@ module.exports = { }, async query(pool, sql) { const resp = await pool.request().query(sql); - return resp.recordset; + // console.log(Object.keys(resp.recordset)); + const columns = _.sortBy(_.values(resp.recordset.columns), 'index'); + return { rows: resp.recordset, columns }; }, async getVersion(pool) { - const { version } = (await this.query(pool, 'SELECT @@VERSION AS version'))[0]; + const { version } = (await this.query(pool, 'SELECT @@VERSION AS version')).rows[0]; return { version }; }, async listDatabases(pool) { - const res = await this.query(pool, 'SELECT name FROM sys.databases order by name'); - return res; + const { rows } = await this.query(pool, 'SELECT name FROM sys.databases order by name'); + return rows; }, async analyseFull(pool) { const analyser = new MsSqlAnalyser(pool, this); diff --git a/api/src/engines/mysql/index.js b/api/src/engines/mysql/index.js index d2dd6a4fa..2fc9a4174 100644 --- a/api/src/engines/mysql/index.js +++ b/api/src/engines/mysql/index.js @@ -9,17 +9,17 @@ module.exports = { return new Promise((resolve, reject) => { connection.query(sql, function(error, results, fields) { if (error) reject(error); - resolve(results); + resolve({ rows: results }); }); }); }, async getVersion(connection) { - const rows = await this.query(connection, "show variables like 'version'"); + const { rows } = await this.query(connection, "show variables like 'version'"); const version = rows[0].Value; return { version }; }, async listDatabases(connection) { - const res = await this.query(connection, 'show databases'); - return res.map(x => ({ name: x.Database })); + const { rows } = await this.query(connection, 'show databases'); + return rows.map(x => ({ name: x.Database })); }, }; diff --git a/api/src/engines/postgres/index.js b/api/src/engines/postgres/index.js index e179bce7d..b14840441 100644 --- a/api/src/engines/postgres/index.js +++ b/api/src/engines/postgres/index.js @@ -8,15 +8,15 @@ module.exports = { }, async query(client, sql) { const res = await client.query(sql); - return res.rows; + return { rows: res.rows }; }, async getVersion(client) { - const rows = await this.query(client, 'SELECT version()'); + const { rows } = await this.query(client, 'SELECT version()'); const { version } = rows[0]; return { version }; }, async listDatabases(client) { - const res = await this.query(client, 'SELECT datname AS name FROM pg_database WHERE datistemplate = false'); - return res; + const { rows } = await this.query(client, 'SELECT datname AS name FROM pg_database WHERE datistemplate = false'); + return rows; }, }; diff --git a/api/src/proc/databaseConnectionProcess.js b/api/src/proc/databaseConnectionProcess.js index 60d42bf1e..67426c130 100644 --- a/api/src/proc/databaseConnectionProcess.js +++ b/api/src/proc/databaseConnectionProcess.js @@ -2,6 +2,7 @@ const engines = require('../engines'); let systemConnection; let storedConnection; +let afterConnectCallbacks = []; async function handleFullRefresh() { const driver = engines(storedConnection); @@ -16,12 +17,24 @@ async function handleConnect(connection) { systemConnection = await driver.connect(storedConnection); handleFullRefresh(); setInterval(handleFullRefresh, 30 * 1000); + for (const [resolve, reject] of afterConnectCallbacks) { + resolve(); + } + afterConnectCallbacks = []; +} + +function waitConnected() { + if (systemConnection) return Promise.resolve(); + return new Promise((resolve, reject) => { + afterConnectCallbacks.push([resolve, reject]); + }); } async function handleTableData({ msgid, schemaName, pureName }) { + await waitConnected(); const driver = engines(storedConnection); - const res = await driver.query(systemConnection, `SELECT TOP(100) FROM ${pureName}`); - process.send({ msgtype: 'response', msgid, rows: res }); + const res = await driver.query(systemConnection, `SELECT TOP(100) * FROM ${pureName}`); + process.send({ msgtype: 'response', msgid, ...res }); } const messageHandlers = { diff --git a/api/src/types.ts b/api/src/types.ts index 436fc4474..b306a8fc7 100644 --- a/api/src/types.ts +++ b/api/src/types.ts @@ -1,8 +1,12 @@ import { ChildProcess } from 'child_process'; +export interface QueryResult { + rows: any[]; +} + export interface EngineDriver { connect({ server, port, user, password }); - query(pool, sql: string): Promise; + query(pool, sql: string): Promise; getVersion(pool): Promise; listDatabases(pool): Promise<{ name: string }[]>; analyseFull(pool): Promise; diff --git a/web/src/appobj/tableAppObject.js b/web/src/appobj/tableAppObject.js index 5531e877d..c74869c40 100644 --- a/web/src/appobj/tableAppObject.js +++ b/web/src/appobj/tableAppObject.js @@ -1,10 +1,10 @@ import React from 'react'; -import uuidv1 from 'uuid/v1'; import { TableIcon } from '../icons'; import { DropDownMenuItem } from '../modals/DropDownMenu'; import showModal from '../modals/showModal'; import ConnectionModal from '../modals/ConnectionModal'; import axios from '../utility/axios'; +import { openNewTab } from '../utility/common'; function Menu({ data, makeAppObj }) { const handleEdit = () => { @@ -21,25 +21,22 @@ function Menu({ data, makeAppObj }) { ); } -export default function tableAppObject({ pureName, schemaName }, { setOpenedTabs }) { +export default function tableAppObject({ conid, database, pureName, schemaName }, { setOpenedTabs }) { const title = schemaName ? `${schemaName}.${pureName}` : pureName; const key = title; const Icon = TableIcon; const onClick = ({ schemaName, pureName }) => { - const tabid = uuidv1(); - setOpenedTabs(files => [ - ...files, - { - tabid, - title: pureName, - icon: 'table2.svg', - tabComponent: 'TableDataTab', - props: { - schemaName, - pureName, - }, + openNewTab(setOpenedTabs, { + title: pureName, + icon: 'table2.svg', + tabComponent: 'TableDataTab', + props: { + schemaName, + pureName, + conid, + database, }, - ]); + }); }; return { title, key, Icon, Menu, onClick }; diff --git a/web/src/tabs/TableDataTab.js b/web/src/tabs/TableDataTab.js index bb553fb43..bc90d8fdd 100644 --- a/web/src/tabs/TableDataTab.js +++ b/web/src/tabs/TableDataTab.js @@ -1,5 +1,34 @@ import React from 'react'; +import useFetch from '../utility/useFetch'; +import { scryRenderedComponentsWithType } from 'react-dom/test-utils'; -export default function TableDataTab({ schemaName, pureName }) { - return
{pureName}
; +export default function TableDataTab({ conid, database, schemaName, pureName }) { + // return pureName; + const data = useFetch({ + url: 'tables/table-data', + params: { + conid, + database, + schemaName, + pureName, + }, + }); + const { rows, columns } = data || {}; + if (!columns || !rows) return null; + return ( + + + {columns.map(col => ( + + ))} + + {rows.map((row, index) => ( + + {columns.map(col => ( + + ))} + + ))} +
{col.name}
{row[col.name]}
+ ); } diff --git a/web/src/utility/common.js b/web/src/utility/common.js index a9e9d09f9..2cc8af041 100644 --- a/web/src/utility/common.js +++ b/web/src/utility/common.js @@ -1,3 +1,5 @@ +import uuidv1 from 'uuid/v1'; + export class LoadingToken { constructor() { this.isCanceled = false; @@ -11,3 +13,15 @@ export class LoadingToken { export function sleep(milliseconds) { return new Promise(resolve => window.setTimeout(() => resolve(null), milliseconds)); } + +export function openNewTab(setOpenedTabs, newTab) { + const tabid = uuidv1(); + setOpenedTabs(files => [ + ...(files || []).map(x => ({ ...x, selected: false })), + { + tabid, + selected: true, + ...newTab, + }, + ]); +} diff --git a/web/src/utility/useFetch.js b/web/src/utility/useFetch.js index b2002c945..b030b9a79 100644 --- a/web/src/utility/useFetch.js +++ b/web/src/utility/useFetch.js @@ -2,7 +2,13 @@ import React from 'react'; import axios from './axios'; import useSocket from './SocketProvider'; -export default function useFetch({ url, defaultValue = undefined, reloadTrigger = undefined, ...config }) { +export default function useFetch({ + url, + params = undefined, + defaultValue = undefined, + reloadTrigger = undefined, + ...config +}) { const [value, setValue] = React.useState(defaultValue); const [loadCounter, setLoadCounter] = React.useState(0); const socket = useSocket(); @@ -14,6 +20,7 @@ export default function useFetch({ url, defaultValue = undefined, reloadTrigger async function loadValue() { const resp = await axios.request({ method: 'get', + params, url, ...config, }); @@ -27,7 +34,7 @@ export default function useFetch({ url, defaultValue = undefined, reloadTrigger socket.off(reloadTrigger, handleReload); }; } - }, [url, socket, loadCounter]); + }, [url, params, socket, loadCounter]); return value; }