mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-18 02:06:01 +00:00
ms sql analyse - table list
This commit is contained in:
47
api/src/controllers/databaseConnections.js
Normal file
47
api/src/controllers/databaseConnections.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const fp = require('lodash/fp');
|
||||||
|
const connections = require('./connections');
|
||||||
|
const socket = require('../utility/socket');
|
||||||
|
const { fork } = require('child_process');
|
||||||
|
const DatabaseAnalyser = require('../engines/default/DatabaseAnalyser')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/** @type {import('../types').OpenedDatabaseConnection[]} */
|
||||||
|
opened: [],
|
||||||
|
|
||||||
|
handle_structure(id, database, { structure }) {
|
||||||
|
const existing = this.opened.find(x => x.id == id && x.database == database);
|
||||||
|
if (!existing) return;
|
||||||
|
existing.structure = structure;
|
||||||
|
socket.emit(`database-structure-changed-${id}-${database}`);
|
||||||
|
},
|
||||||
|
handle_error(id, { error }) {
|
||||||
|
console.log(error);
|
||||||
|
},
|
||||||
|
|
||||||
|
async ensureOpened(id, database) {
|
||||||
|
const existing = this.opened.find(x => x.id == id && x.database == database);
|
||||||
|
if (existing) return existing;
|
||||||
|
const connection = await connections.get({ id });
|
||||||
|
const subprocess = fork(`${__dirname}/../proc/databaseConnectionProcess.js`);
|
||||||
|
const newOpened = {
|
||||||
|
id,
|
||||||
|
database,
|
||||||
|
subprocess,
|
||||||
|
structure: DatabaseAnalyser.createEmptyStructure(),
|
||||||
|
connection,
|
||||||
|
};
|
||||||
|
this.opened.push(newOpened);
|
||||||
|
// @ts-ignore
|
||||||
|
subprocess.on('message', ({ msgtype, ...message }) => {
|
||||||
|
this[`handle_${msgtype}`](id, database, message);
|
||||||
|
});
|
||||||
|
subprocess.send({ msgtype: 'connect', ...connection });
|
||||||
|
return newOpened;
|
||||||
|
},
|
||||||
|
|
||||||
|
listTables_meta: 'get',
|
||||||
|
async listTables({ id, database }) {
|
||||||
|
const opened = await this.ensureOpened(id, database);
|
||||||
|
return opened.structure.tables; // .map(fp.pick(['tableName', 'schemaName']));
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
|
|
||||||
class DatabaseAnalyser {
|
class DatabaseAnalyser {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('../default/types').EngineDriver} driver
|
* @param {import('../../types').EngineDriver} driver
|
||||||
*/
|
*/
|
||||||
constructor(pool, driver) {
|
constructor(pool, driver) {
|
||||||
this.pool = pool;
|
this.pool = pool;
|
||||||
this.driver = driver;
|
this.driver = driver;
|
||||||
|
this.result = DatabaseAnalyser.createEmptyStructure();
|
||||||
}
|
}
|
||||||
runAnalysis() {}
|
async runAnalysis() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {import('../../types').DatabaseInfo} */
|
||||||
|
DatabaseAnalyser.createEmptyStructure = () => ({
|
||||||
|
tables: [],
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = DatabaseAnalyser;
|
module.exports = DatabaseAnalyser;
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface EngineDriver {
|
|
||||||
connect({ server, port, user, password });
|
|
||||||
query(pool, sql: string): [];
|
|
||||||
getVersion(pool): string;
|
|
||||||
listDatabases(pool): [{ name: string }];
|
|
||||||
analyseFull(pool);
|
|
||||||
analyseIncremental(pool);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
module.exports = connection => {
|
|
||||||
|
/** @return {import('../types').EngineDriver} */
|
||||||
|
function getDriver(connection) {
|
||||||
const { engine } = connection;
|
const { engine } = connection;
|
||||||
return require(`./${engine}`);
|
return require(`./${engine}`);
|
||||||
};
|
|
||||||
|
}
|
||||||
|
module.exports = getDriver;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const DatabaseAnalayser = require('../default/DatabaseAnalyser');
|
const DatabaseAnalayser = require('../default/DatabaseAnalyser');
|
||||||
|
|
||||||
|
/** @returns {Promise<string>} */
|
||||||
async function loadQuery(name) {
|
async function loadQuery(name) {
|
||||||
return await fs.readFile(path.join(__dirname, name), 'utf-8');
|
return await fs.readFile(path.join(__dirname, name), 'utf-8');
|
||||||
}
|
}
|
||||||
@@ -13,8 +13,27 @@ class MsSqlAnalyser extends DatabaseAnalayser {
|
|||||||
super(pool, driver);
|
super(pool, driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createQuery(
|
||||||
|
resFileName,
|
||||||
|
tables = false,
|
||||||
|
views = false,
|
||||||
|
procedures = false,
|
||||||
|
functions = false,
|
||||||
|
triggers = false
|
||||||
|
) {
|
||||||
|
let res = await loadQuery(resFileName);
|
||||||
|
res = res.replace('=[OBJECT_ID_CONDITION]', ' is not null');
|
||||||
|
return res;
|
||||||
|
}
|
||||||
async runAnalysis() {
|
async runAnalysis() {
|
||||||
const tables = this.driver.query(this.pool, await loadQuery('tables.sql'));
|
const tables = await this.driver.query(this.pool, await this.createQuery('tables.sql'));
|
||||||
|
// for (const table of tables) {
|
||||||
|
// table.name = {
|
||||||
|
// schema: table.schemaName,
|
||||||
|
// name: table.tableName,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
this.result.tables = tables;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ const mssql = require('mssql');
|
|||||||
const MsSqlAnalyser = require('./MsSqlAnalyser');
|
const MsSqlAnalyser = require('./MsSqlAnalyser');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async connect({ server, port, user, password }) {
|
async connect({ server, port, user, password, database }) {
|
||||||
const pool = await mssql.connect({ server, port, user, password });
|
const pool = await mssql.connect({ server, port, user, password, database });
|
||||||
return pool;
|
return pool;
|
||||||
},
|
},
|
||||||
async query(pool, sql) {
|
async query(pool, sql) {
|
||||||
@@ -19,9 +19,9 @@ module.exports = {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
async analyseFull(pool) {
|
async analyseFull(pool) {
|
||||||
|
const analyser = new MsSqlAnalyser(pool, this);
|
||||||
},
|
await analyser.runAnalysis();
|
||||||
async analyseIncremental(pool) {
|
return analyser.result;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
async analyseIncremental(pool) {},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
select
|
select
|
||||||
o.name as tableName, s.name as schemaName, o.objectId,
|
o.name as tableName, s.name as schemaName, o.object_id,
|
||||||
o.createDate, o.modifyDate
|
o.create_date, o.modify_date
|
||||||
from sys.tables o
|
from sys.tables o
|
||||||
inner join sys.schemas s on o.schema_id = s.schema_id
|
inner join sys.schemas s on o.schema_id = s.schema_id
|
||||||
where o.object_id =[OBJECT_ID_CONDITION]
|
where o.object_id =[OBJECT_ID_CONDITION]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const mysql = require('mysql');
|
const mysql = require('mysql');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async connect({ server, port, user, password }) {
|
async connect({ server, port, user, password, database }) {
|
||||||
const connection = mysql.createConnection({ host: server, port, user, password });
|
const connection = mysql.createConnection({ host: server, port, user, password, database });
|
||||||
return connection;
|
return connection;
|
||||||
},
|
},
|
||||||
async query(connection, sql) {
|
async query(connection, sql) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const { Client } = require('pg');
|
const { Client } = require('pg');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async connect({ server, port, user, password }) {
|
async connect({ server, port, user, password, database }) {
|
||||||
const client = new Client({ host: server, port, user, password, database: 'postgres' });
|
const client = new Client({ host: server, port, user, password, database: database || 'postgres' });
|
||||||
await client.connect();
|
await client.connect();
|
||||||
return client;
|
return client;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const io = require('socket.io');
|
|||||||
const useController = require('./utility/useController');
|
const useController = require('./utility/useController');
|
||||||
const connections = require('./controllers/connections');
|
const connections = require('./controllers/connections');
|
||||||
const serverConnections = require('./controllers/serverConnections');
|
const serverConnections = require('./controllers/serverConnections');
|
||||||
|
const databaseConnections = require('./controllers/databaseConnections');
|
||||||
const socket = require('./utility/socket');
|
const socket = require('./utility/socket');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -23,5 +24,6 @@ app.get('/', (req, res) => {
|
|||||||
|
|
||||||
useController(app, '/connections', connections);
|
useController(app, '/connections', connections);
|
||||||
useController(app, '/server-connections', serverConnections);
|
useController(app, '/server-connections', serverConnections);
|
||||||
|
useController(app, '/database-connections', databaseConnections);
|
||||||
|
|
||||||
server.listen(3000);
|
server.listen(3000);
|
||||||
|
|||||||
37
api/src/proc/databaseConnectionProcess.js
Normal file
37
api/src/proc/databaseConnectionProcess.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const engines = require('../engines');
|
||||||
|
|
||||||
|
let systemConnection;
|
||||||
|
let storedConnection;
|
||||||
|
|
||||||
|
async function handleFullRefresh() {
|
||||||
|
const driver = engines(storedConnection);
|
||||||
|
const structure = await driver.analyseFull(systemConnection);
|
||||||
|
console.log('SENDING STRUCTURE', structure);
|
||||||
|
process.send({ msgtype: 'structure', structure });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleConnect(connection, database) {
|
||||||
|
storedConnection = connection;
|
||||||
|
|
||||||
|
const driver = engines(storedConnection);
|
||||||
|
systemConnection = await driver.connect({ ...storedConnection, database });
|
||||||
|
handleFullRefresh();
|
||||||
|
setInterval(handleFullRefresh, 30 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageHandlers = {
|
||||||
|
connect: handleConnect,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleMessage({ msgtype, database, ...other }) {
|
||||||
|
const handler = messageHandlers[msgtype];
|
||||||
|
await handler(other, database);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('message', async message => {
|
||||||
|
try {
|
||||||
|
await handleMessage(message);
|
||||||
|
} catch (e) {
|
||||||
|
process.send({ msgtype: 'error', error: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
29
api/src/types.ts
Normal file
29
api/src/types.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export interface EngineDriver {
|
||||||
|
connect({ server, port, user, password });
|
||||||
|
query(pool, sql: string): Promise<any[]>;
|
||||||
|
getVersion(pool): Promise<string>;
|
||||||
|
listDatabases(pool): Promise<{ name: string }[]>;
|
||||||
|
analyseFull(pool): Promise<void>;
|
||||||
|
analyseIncremental(pool): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface NameWithSchema {
|
||||||
|
// schema: string;
|
||||||
|
// name: string;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export interface TableInfo {
|
||||||
|
// name: NameWithSchema;
|
||||||
|
tableName: string;
|
||||||
|
schemaName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseInfo {
|
||||||
|
tables: TableInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OpenedDatabaseConnection {
|
||||||
|
id: string;
|
||||||
|
database: string;
|
||||||
|
structure: DatabaseInfo;
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import theme from './theme';
|
import theme from './theme';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -9,10 +11,7 @@ import WidgetContainer from './widgets/WidgetContainer';
|
|||||||
const BodyDiv = styled.div`
|
const BodyDiv = styled.div`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: ${theme.tabsPanel.height}px;
|
top: ${theme.tabsPanel.height}px;
|
||||||
left: ${props =>
|
left: ${props => theme.widgetMenu.iconSize + props.leftPanelWidth}px;
|
||||||
theme.widgetMenu.iconSize +
|
|
||||||
// @ts-ignore
|
|
||||||
props.leftPanelWidth}px;
|
|
||||||
bottom: ${theme.statusBar.height}px;
|
bottom: ${theme.statusBar.height}px;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: ${theme.mainArea.background};
|
background-color: ${theme.mainArea.background};
|
||||||
@@ -41,10 +40,7 @@ const TabsPanel = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: ${props =>
|
left: ${props => theme.widgetMenu.iconSize + props.leftPanelWidth}px;
|
||||||
theme.widgetMenu.iconSize +
|
|
||||||
// @ts-ignore
|
|
||||||
props.leftPanelWidth}px;
|
|
||||||
height: ${theme.tabsPanel.height}px;
|
height: ${theme.tabsPanel.height}px;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: ${theme.tabsPanel.background};
|
background-color: ${theme.tabsPanel.background};
|
||||||
@@ -72,18 +68,10 @@ export default function Screen({ children = undefined }) {
|
|||||||
<WidgetContainer />
|
<WidgetContainer />
|
||||||
</LeftPanel>
|
</LeftPanel>
|
||||||
)}
|
)}
|
||||||
<TabsPanel
|
<TabsPanel leftPanelWidth={leftPanelWidth}>
|
||||||
// @ts-ignore
|
|
||||||
leftPanelWidth={leftPanelWidth}
|
|
||||||
>
|
|
||||||
<FilesTabsPanel></FilesTabsPanel>
|
<FilesTabsPanel></FilesTabsPanel>
|
||||||
</TabsPanel>
|
</TabsPanel>
|
||||||
<BodyDiv
|
<BodyDiv leftPanelWidth={leftPanelWidth}>{children}</BodyDiv>
|
||||||
// @ts-ignore
|
|
||||||
leftPanelWidth={leftPanelWidth}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</BodyDiv>
|
|
||||||
<StausBar></StausBar>
|
<StausBar></StausBar>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,21 +37,56 @@ function SubDatabaseList({ data }) {
|
|||||||
return <AppObjectList list={databases} makeAppObj={databaseAppObject} onObjectClick={handleDatabaseClick} />;
|
return <AppObjectList list={databases} makeAppObj={databaseAppObject} onObjectClick={handleDatabaseClick} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DatabaseWidget() {
|
function ConnectionList() {
|
||||||
const db = useCurrentDatabase();
|
|
||||||
const modalState = useModalState();
|
const modalState = useModalState();
|
||||||
const connections = useFetch({
|
const connections = useFetch({
|
||||||
url: 'connections/list',
|
url: 'connections/list',
|
||||||
reloadTrigger: 'connection-list-changed',
|
reloadTrigger: 'connection-list-changed',
|
||||||
});
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ConnectionModal modalState={modalState} />
|
||||||
|
<button onClick={modalState.open}>Add connection</button>
|
||||||
|
<AppObjectList list={connections} makeAppObj={connectionAppObject} SubItems={SubDatabaseList} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SqlObjectList({ id, database }) {
|
||||||
|
const tables =
|
||||||
|
useFetch({
|
||||||
|
url: `database-connections/list-tables?id=${id}&database=${database}`,
|
||||||
|
reloadTrigger: `database-structure-changed-${id}-${database}`,
|
||||||
|
}) || [];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{tables.map(({ tableName, schemaName }) => (
|
||||||
|
<div key={`${schemaName}.${tableName}`}>{tableName}</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SqlObjectListWrapper() {
|
||||||
|
const db = useCurrentDatabase();
|
||||||
|
|
||||||
|
if (!db) return <div>(Choose database)</div>;
|
||||||
|
const { name, connection } = db;
|
||||||
|
|
||||||
|
return <SqlObjectList id={connection._id} database={name} />;
|
||||||
|
// return <div>tables of {db && db.name}</div>
|
||||||
|
// return <div>tables of {JSON.stringify(db)}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DatabaseWidget() {
|
||||||
return (
|
return (
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
<InnerContainer>
|
<InnerContainer>
|
||||||
<ConnectionModal modalState={modalState} />
|
<ConnectionList />
|
||||||
<button onClick={modalState.open}>Add connection</button>
|
</InnerContainer>
|
||||||
<AppObjectList list={connections} makeAppObj={connectionAppObject} SubItems={SubDatabaseList} />
|
<InnerContainer>
|
||||||
|
<SqlObjectListWrapper />
|
||||||
</InnerContainer>
|
</InnerContainer>
|
||||||
<InnerContainer>tables of {db && db.name}</InnerContainer>
|
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user