mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
load views, procedures, functions
This commit is contained in:
parent
241d8ea647
commit
ea6c31187b
@ -1,17 +1,22 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const fp = require('lodash/fp');
|
||||||
const uuidv1 = require('uuid/v1');
|
const uuidv1 = require('uuid/v1');
|
||||||
const connections = require('./connections');
|
const connections = require('./connections');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
const { fork } = require('child_process');
|
const { fork } = require('child_process');
|
||||||
const DatabaseAnalyser = require('@dbgate/engines/default/DatabaseAnalyser');
|
const DatabaseAnalyser = require('@dbgate/engines/default/DatabaseAnalyser');
|
||||||
|
|
||||||
|
function pickObjectNames(array) {
|
||||||
|
return _.sortBy(array, (x) => `${x.schemaName}.${x.pureName}`).map(fp.pick(['pureName', 'schemaName']));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/** @type {import('@dbgate/types').OpenedDatabaseConnection[]} */
|
/** @type {import('@dbgate/types').OpenedDatabaseConnection[]} */
|
||||||
opened: [],
|
opened: [],
|
||||||
requests: {},
|
requests: {},
|
||||||
|
|
||||||
handle_structure(conid, database, { structure }) {
|
handle_structure(conid, database, { structure }) {
|
||||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
const existing = this.opened.find((x) => x.conid == conid && x.database == database);
|
||||||
if (!existing) return;
|
if (!existing) return;
|
||||||
existing.structure = structure;
|
existing.structure = structure;
|
||||||
socket.emit(`database-structure-changed-${conid}-${database}`);
|
socket.emit(`database-structure-changed-${conid}-${database}`);
|
||||||
@ -27,7 +32,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async ensureOpened(conid, database) {
|
async ensureOpened(conid, database) {
|
||||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
const existing = this.opened.find((x) => x.conid == conid && x.database == database);
|
||||||
if (existing) return existing;
|
if (existing) return existing;
|
||||||
const connection = await connections.get({ conid });
|
const connection = await connections.get({ conid });
|
||||||
const subprocess = fork(process.argv[1], ['databaseConnectionProcess']);
|
const subprocess = fork(process.argv[1], ['databaseConnectionProcess']);
|
||||||
@ -60,10 +65,14 @@ module.exports = {
|
|||||||
listObjects_meta: 'get',
|
listObjects_meta: 'get',
|
||||||
async listObjects({ conid, database }) {
|
async listObjects({ conid, database }) {
|
||||||
const opened = await this.ensureOpened(conid, database);
|
const opened = await this.ensureOpened(conid, database);
|
||||||
const { tables } = opened.structure;
|
const types = ['tables', 'views', 'procedures', 'functions', 'triggers'];
|
||||||
return {
|
return types.reduce(
|
||||||
tables: _.sortBy(tables, x => `${x.schemaName}.${x.pureName}`),
|
(res, type) => ({
|
||||||
}; // .map(fp.pick(['tableName', 'schemaName']));
|
...res,
|
||||||
|
[type]: pickObjectNames(opened.structure[type]),
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
queryData_meta: 'post',
|
queryData_meta: 'post',
|
||||||
|
@ -208,25 +208,64 @@ class MsSqlAnalyser extends DatabaseAnalayser {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
async _runAnalysis() {
|
async _runAnalysis() {
|
||||||
const tables = await this.driver.query(this.pool, this.createQuery('tables', ['tables']));
|
const tablesRows = await this.driver.query(this.pool, this.createQuery('tables', ['tables']));
|
||||||
const columns = await this.driver.query(this.pool, this.createQuery('columns', ['tables']));
|
const columnsRows = await this.driver.query(this.pool, this.createQuery('columns', ['tables']));
|
||||||
const pkColumns = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables']));
|
const pkColumnsRows = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables']));
|
||||||
const fkColumns = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables']));
|
const fkColumnsRows = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables']));
|
||||||
|
|
||||||
|
const sqlCodeRows = await this.driver.query(
|
||||||
|
this.pool,
|
||||||
|
this.createQuery('loadSqlCode', ['views', 'procedures', 'functions', 'triggers'])
|
||||||
|
);
|
||||||
|
const getCreateSql = (row) =>
|
||||||
|
sqlCodeRows.rows
|
||||||
|
.filter((x) => x.pureName == row.pureName && x.schemaName == row.schemaName)
|
||||||
|
.map((x) => x.codeText)
|
||||||
|
.join('');
|
||||||
|
const viewsRows = await this.driver.query(this.pool, this.createQuery('views', ['views']));
|
||||||
|
const programmableRows = await this.driver.query(
|
||||||
|
this.pool,
|
||||||
|
this.createQuery('programmables', ['procedures', 'functions'])
|
||||||
|
);
|
||||||
|
|
||||||
|
const tables = tablesRows.rows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
columns: columnsRows.rows
|
||||||
|
.filter((col) => col.objectId == row.objectId)
|
||||||
|
.map(({ isNullable, isIdentity, ...col }) => ({
|
||||||
|
...col,
|
||||||
|
notNull: !isNullable,
|
||||||
|
autoIncrement: !!isIdentity,
|
||||||
|
commonType: detectType(col),
|
||||||
|
})),
|
||||||
|
primaryKey: extractPrimaryKeys(row, pkColumnsRows.rows),
|
||||||
|
foreignKeys: extractForeignKeys(row, fkColumnsRows.rows),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const views = viewsRows.rows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
createSql: getCreateSql(row),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const procedures = programmableRows.rows
|
||||||
|
.filter((x) => x.sqlObjectType.trim() == 'P')
|
||||||
|
.map((row) => ({
|
||||||
|
...row,
|
||||||
|
createSql: getCreateSql(row),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const functions = programmableRows.rows
|
||||||
|
.filter((x) => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim()))
|
||||||
|
.map((row) => ({
|
||||||
|
...row,
|
||||||
|
createSql: getCreateSql(row),
|
||||||
|
}));
|
||||||
|
|
||||||
return this.mergeAnalyseResult({
|
return this.mergeAnalyseResult({
|
||||||
tables: tables.rows.map((table) => ({
|
tables,
|
||||||
...table,
|
views,
|
||||||
columns: columns.rows
|
procedures,
|
||||||
.filter((col) => col.objectId == table.objectId)
|
functions,
|
||||||
.map(({ isNullable, isIdentity, ...col }) => ({
|
|
||||||
...col,
|
|
||||||
notNull: !isNullable,
|
|
||||||
autoIncrement: !!isIdentity,
|
|
||||||
commonType: detectType(col),
|
|
||||||
})),
|
|
||||||
primaryKey: extractPrimaryKeys(table, pkColumns.rows),
|
|
||||||
foreignKeys: extractForeignKeys(table, fkColumns.rows),
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,9 @@ const foreignKeys = require('./foreignKeys');
|
|||||||
const primaryKeys = require('./primaryKeys');
|
const primaryKeys = require('./primaryKeys');
|
||||||
const tables = require('./tables');
|
const tables = require('./tables');
|
||||||
const modifications = require('./modifications');
|
const modifications = require('./modifications');
|
||||||
|
const loadSqlCode = require('./loadSqlCode');
|
||||||
|
const views = require('./views');
|
||||||
|
const programmables = require('./programmables');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
columns,
|
columns,
|
||||||
@ -10,4 +13,7 @@ module.exports = {
|
|||||||
foreignKeys,
|
foreignKeys,
|
||||||
primaryKeys,
|
primaryKeys,
|
||||||
modifications,
|
modifications,
|
||||||
|
loadSqlCode,
|
||||||
|
views,
|
||||||
|
programmables,
|
||||||
};
|
};
|
||||||
|
8
packages/engines/mssql/sql/loadSqlCode.js
Normal file
8
packages/engines/mssql/sql/loadSqlCode.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = `
|
||||||
|
select s.name as pureName, u.name as schemaName, c.text AS codeText
|
||||||
|
from sys.objects s
|
||||||
|
inner join sys.syscomments c on s.object_id = c.id
|
||||||
|
inner join sys.schemas u on u.schema_id = s.schema_id
|
||||||
|
where (s.object_id =[OBJECT_ID_CONDITION])
|
||||||
|
order by u.name, s.name, c.colid
|
||||||
|
`;
|
6
packages/engines/mssql/sql/programmables.js
Normal file
6
packages/engines/mssql/sql/programmables.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = `
|
||||||
|
select o.name as pureName, s.name as schemaName, o.object_id as objectId, o.create_date as createDate, o.modify_date as modifyDate, o.type as sqlObjectType
|
||||||
|
from sys.objects o
|
||||||
|
inner join sys.schemas s on o.schema_id = s.schema_id
|
||||||
|
where o.type in ('P', 'IF', 'FN', 'TF') and o.object_id =[OBJECT_ID_CONDITION]
|
||||||
|
`;
|
10
packages/engines/mssql/sql/views.js
Normal file
10
packages/engines/mssql/sql/views.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT
|
||||||
|
o.name as pureName,
|
||||||
|
u.name as schemaName,
|
||||||
|
o.object_id as objectId,
|
||||||
|
o.create_date as createDate,
|
||||||
|
o.modify_date as modifyDate
|
||||||
|
FROM sys.objects o INNER JOIN sys.schemas u ON u.schema_id=o.schema_id
|
||||||
|
WHERE type in ('V') and o.object_id =[OBJECT_ID_CONDITION]
|
||||||
|
`;
|
16
packages/web/src/appobj/databaseObjectAppObject.js
Normal file
16
packages/web/src/appobj/databaseObjectAppObject.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import tableAppObject from './tableAppObject';
|
||||||
|
import viewAppObject from './viewAppObject';
|
||||||
|
|
||||||
|
const databaseObjectAppObject = () => ({ objectTypeField, ...other }, props) => {
|
||||||
|
switch (objectTypeField) {
|
||||||
|
case 'tables':
|
||||||
|
// @ts-ignore
|
||||||
|
return tableAppObject()(other, props);
|
||||||
|
case 'views':
|
||||||
|
// @ts-ignore
|
||||||
|
return viewAppObject()(other, props);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default databaseObjectAppObject;
|
@ -58,9 +58,10 @@ const tableAppObject = () => ({ conid, database, pureName, schemaName }, { setOp
|
|||||||
database,
|
database,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const matcher = filter => filterName(filter, pureName);
|
const matcher = (filter) => filterName(filter, pureName);
|
||||||
|
const groupTitle = 'Tables';
|
||||||
|
|
||||||
return { title, key, Icon, Menu, onClick, matcher };
|
return { title, key, Icon, Menu, onClick, matcher, groupTitle };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default tableAppObject;
|
export default tableAppObject;
|
||||||
|
74
packages/web/src/appobj/viewAppObject.js
Normal file
74
packages/web/src/appobj/viewAppObject.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ViewIcon } from '../icons';
|
||||||
|
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||||
|
import { openNewTab } from '../utility/common';
|
||||||
|
import getConnectionInfo from '../utility/getConnectionInfo';
|
||||||
|
import fullDisplayName from '../utility/fullDisplayName';
|
||||||
|
import { filterName } from '@dbgate/datalib';
|
||||||
|
|
||||||
|
// async function openTableDetail(setOpenedTabs, tabComponent, { schemaName, pureName, conid, database }) {
|
||||||
|
// const connection = await getConnectionInfo(conid);
|
||||||
|
// const tooltip = `${connection.displayName || connection.server}\n${database}\n${fullDisplayName({
|
||||||
|
// schemaName,
|
||||||
|
// pureName,
|
||||||
|
// })}`;
|
||||||
|
|
||||||
|
// openNewTab(setOpenedTabs, {
|
||||||
|
// title: pureName,
|
||||||
|
// tooltip,
|
||||||
|
// icon: 'table2.svg',
|
||||||
|
// tabComponent,
|
||||||
|
// props: {
|
||||||
|
// schemaName,
|
||||||
|
// pureName,
|
||||||
|
// conid,
|
||||||
|
// database,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function Menu({ data, makeAppObj, setOpenedTabs }) {
|
||||||
|
// const handleOpenData = () => {
|
||||||
|
// openTableDetail(setOpenedTabs, 'TableDataTab', data);
|
||||||
|
// };
|
||||||
|
// const handleOpenStructure = () => {
|
||||||
|
// openTableDetail(setOpenedTabs, 'TableStructureTab', data);
|
||||||
|
// };
|
||||||
|
// const handleOpenCreateScript = () => {
|
||||||
|
// openTableDetail(setOpenedTabs, 'TableCreateScriptTab', data);
|
||||||
|
// };
|
||||||
|
// return (
|
||||||
|
// <>
|
||||||
|
// <DropDownMenuItem onClick={handleOpenData}>Open data</DropDownMenuItem>
|
||||||
|
// <DropDownMenuItem onClick={handleOpenStructure}>Open structure</DropDownMenuItem>
|
||||||
|
// <DropDownMenuItem onClick={handleOpenCreateScript}>Create SQL</DropDownMenuItem>
|
||||||
|
// </>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
const viewAppObject = () => ({ conid, database, pureName, schemaName }, { setOpenedTabs }) => {
|
||||||
|
const title = schemaName ? `${schemaName}.${pureName}` : pureName;
|
||||||
|
const key = title;
|
||||||
|
const Icon = ViewIcon;
|
||||||
|
// const onClick = ({ schemaName, pureName }) => {
|
||||||
|
// openTableDetail(setOpenedTabs, 'TableDataTab', {
|
||||||
|
// schemaName,
|
||||||
|
// pureName,
|
||||||
|
// conid,
|
||||||
|
// database,
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
const matcher = (filter) => filterName(filter, pureName);
|
||||||
|
const groupTitle = 'Views';
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
key,
|
||||||
|
Icon,
|
||||||
|
// Menu, onClick,
|
||||||
|
matcher,
|
||||||
|
groupTitle,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default viewAppObject;
|
@ -57,6 +57,8 @@ export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPo
|
|||||||
|
|
||||||
const handleExecute = async () => {
|
const handleExecute = async () => {
|
||||||
setExecuteNumber((num) => num + 1);
|
setExecuteNumber((num) => num + 1);
|
||||||
|
const selectedText = editorRef.current.editor.getSelectedText();
|
||||||
|
|
||||||
let sesid = sessionId;
|
let sesid = sessionId;
|
||||||
if (!sesid) {
|
if (!sesid) {
|
||||||
const resp = await axios.post('sessions/create', {
|
const resp = await axios.post('sessions/create', {
|
||||||
@ -68,7 +70,7 @@ export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPo
|
|||||||
}
|
}
|
||||||
await axios.post('sessions/execute-query', {
|
await axios.post('sessions/execute-query', {
|
||||||
sesid,
|
sesid,
|
||||||
sql: queryText,
|
sql: selectedText || queryText,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -99,7 +101,11 @@ export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPo
|
|||||||
/>
|
/>
|
||||||
<ResultTabs sessionId={sessionId} executeNumber={executeNumber}>
|
<ResultTabs sessionId={sessionId} executeNumber={executeNumber}>
|
||||||
<TabPage label="Messages" key="messages">
|
<TabPage label="Messages" key="messages">
|
||||||
<SessionMessagesView sessionId={sessionId} onMessageClick={handleMesageClick} executeNumber={executeNumber} />
|
<SessionMessagesView
|
||||||
|
sessionId={sessionId}
|
||||||
|
onMessageClick={handleMesageClick}
|
||||||
|
executeNumber={executeNumber}
|
||||||
|
/>
|
||||||
</TabPage>
|
</TabPage>
|
||||||
</ResultTabs>
|
</ResultTabs>
|
||||||
</VerticalSplitter>
|
</VerticalSplitter>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
import useFetch from '../utility/useFetch';
|
import useFetch from '../utility/useFetch';
|
||||||
import { AppObjectList } from '../appobj/AppObjectList';
|
import { AppObjectList } from '../appobj/AppObjectList';
|
||||||
@ -9,6 +10,7 @@ import { useSetCurrentDatabase, useCurrentDatabase } from '../utility/globalStat
|
|||||||
import tableAppObject from '../appobj/tableAppObject';
|
import tableAppObject from '../appobj/tableAppObject';
|
||||||
import theme from '../theme';
|
import theme from '../theme';
|
||||||
import InlineButton from './InlineButton';
|
import InlineButton from './InlineButton';
|
||||||
|
import databaseObjectAppObject from '../appobj/databaseObjectAppObject';
|
||||||
|
|
||||||
const SearchBoxWrapper = styled.div`
|
const SearchBoxWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -46,7 +48,7 @@ const Input = styled.input`
|
|||||||
|
|
||||||
function SubDatabaseList({ data }) {
|
function SubDatabaseList({ data }) {
|
||||||
const setDb = useSetCurrentDatabase();
|
const setDb = useSetCurrentDatabase();
|
||||||
const handleDatabaseClick = database => {
|
const handleDatabaseClick = (database) => {
|
||||||
setDb({
|
setDb({
|
||||||
...database,
|
...database,
|
||||||
connection: data,
|
connection: data,
|
||||||
@ -59,7 +61,7 @@ function SubDatabaseList({ data }) {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<AppObjectList
|
<AppObjectList
|
||||||
list={(databases || []).map(db => ({ ...db, connection: data }))}
|
list={(databases || []).map((db) => ({ ...db, connection: data }))}
|
||||||
makeAppObj={databaseAppObject({ boldCurrentDatabase: true })}
|
makeAppObj={databaseAppObject({ boldCurrentDatabase: true })}
|
||||||
onObjectClick={handleDatabaseClick}
|
onObjectClick={handleDatabaseClick}
|
||||||
/>
|
/>
|
||||||
@ -75,7 +77,7 @@ function ConnectionList() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchBoxWrapper>
|
<SearchBoxWrapper>
|
||||||
<Input type="text" placeholder="Search connection" value={filter} onChange={e => setFilter(e.target.value)} />
|
<Input type="text" placeholder="Search connection" value={filter} onChange={(e) => setFilter(e.target.value)} />
|
||||||
<InlineButton>Refresh</InlineButton>
|
<InlineButton>Refresh</InlineButton>
|
||||||
</SearchBoxWrapper>
|
</SearchBoxWrapper>
|
||||||
|
|
||||||
@ -96,8 +98,13 @@ function SqlObjectList({ conid, database }) {
|
|||||||
url: `database-connections/list-objects?conid=${conid}&database=${database}`,
|
url: `database-connections/list-objects?conid=${conid}&database=${database}`,
|
||||||
reloadTrigger: `database-structure-changed-${conid}-${database}`,
|
reloadTrigger: `database-structure-changed-${conid}-${database}`,
|
||||||
});
|
});
|
||||||
const { tables } = objects || {};
|
|
||||||
const [filter, setFilter] = React.useState('');
|
const [filter, setFilter] = React.useState('');
|
||||||
|
const objectList = _.flatten(
|
||||||
|
['tables', 'views'].map((objectTypeField) =>
|
||||||
|
((objects || {})[objectTypeField] || []).map((obj) => ({ ...obj, objectTypeField }))
|
||||||
|
)
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchBoxWrapper>
|
<SearchBoxWrapper>
|
||||||
@ -105,15 +112,15 @@ function SqlObjectList({ conid, database }) {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Search tables or objects"
|
placeholder="Search tables or objects"
|
||||||
value={filter}
|
value={filter}
|
||||||
onChange={e => setFilter(e.target.value)}
|
onChange={(e) => setFilter(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<InlineButton>Refresh</InlineButton>
|
<InlineButton>Refresh</InlineButton>
|
||||||
</SearchBoxWrapper>
|
</SearchBoxWrapper>
|
||||||
<InnerContainer>
|
<InnerContainer>
|
||||||
<AppObjectList
|
<AppObjectList
|
||||||
list={(tables || []).map(x => ({ ...x, conid, database }))}
|
list={objectList.map((x) => ({ ...x, conid, database }))}
|
||||||
makeAppObj={tableAppObject()}
|
makeAppObj={databaseObjectAppObject()}
|
||||||
groupFunc={appobj => 'Tables'}
|
groupFunc={(appobj) => appobj.groupTitle}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
/>
|
/>
|
||||||
</InnerContainer>
|
</InnerContainer>
|
||||||
|
Loading…
Reference in New Issue
Block a user