diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js
index 79d334b8..f40c16b8 100644
--- a/packages/api/src/controllers/databaseConnections.js
+++ b/packages/api/src/controllers/databaseConnections.js
@@ -1,17 +1,22 @@
const _ = require('lodash');
+const fp = require('lodash/fp');
const uuidv1 = require('uuid/v1');
const connections = require('./connections');
const socket = require('../utility/socket');
const { fork } = require('child_process');
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 = {
/** @type {import('@dbgate/types').OpenedDatabaseConnection[]} */
opened: [],
requests: {},
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;
existing.structure = structure;
socket.emit(`database-structure-changed-${conid}-${database}`);
@@ -27,7 +32,7 @@ module.exports = {
},
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;
const connection = await connections.get({ conid });
const subprocess = fork(process.argv[1], ['databaseConnectionProcess']);
@@ -60,10 +65,14 @@ module.exports = {
listObjects_meta: 'get',
async listObjects({ conid, database }) {
const opened = await this.ensureOpened(conid, database);
- const { tables } = opened.structure;
- return {
- tables: _.sortBy(tables, x => `${x.schemaName}.${x.pureName}`),
- }; // .map(fp.pick(['tableName', 'schemaName']));
+ const types = ['tables', 'views', 'procedures', 'functions', 'triggers'];
+ return types.reduce(
+ (res, type) => ({
+ ...res,
+ [type]: pickObjectNames(opened.structure[type]),
+ }),
+ {}
+ );
},
queryData_meta: 'post',
diff --git a/packages/engines/mssql/MsSqlAnalyser.js b/packages/engines/mssql/MsSqlAnalyser.js
index 2827ed0f..8bed1262 100644
--- a/packages/engines/mssql/MsSqlAnalyser.js
+++ b/packages/engines/mssql/MsSqlAnalyser.js
@@ -208,25 +208,64 @@ class MsSqlAnalyser extends DatabaseAnalayser {
return res;
}
async _runAnalysis() {
- const tables = await this.driver.query(this.pool, this.createQuery('tables', ['tables']));
- const columns = await this.driver.query(this.pool, this.createQuery('columns', ['tables']));
- const pkColumns = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables']));
- const fkColumns = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables']));
+ const tablesRows = await this.driver.query(this.pool, this.createQuery('tables', ['tables']));
+ const columnsRows = await this.driver.query(this.pool, this.createQuery('columns', ['tables']));
+ const pkColumnsRows = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['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({
- tables: tables.rows.map((table) => ({
- ...table,
- columns: columns.rows
- .filter((col) => col.objectId == table.objectId)
- .map(({ isNullable, isIdentity, ...col }) => ({
- ...col,
- notNull: !isNullable,
- autoIncrement: !!isIdentity,
- commonType: detectType(col),
- })),
- primaryKey: extractPrimaryKeys(table, pkColumns.rows),
- foreignKeys: extractForeignKeys(table, fkColumns.rows),
- })),
+ tables,
+ views,
+ procedures,
+ functions,
});
}
diff --git a/packages/engines/mssql/sql/index.js b/packages/engines/mssql/sql/index.js
index 3213ee5a..cd99b326 100644
--- a/packages/engines/mssql/sql/index.js
+++ b/packages/engines/mssql/sql/index.js
@@ -3,6 +3,9 @@ const foreignKeys = require('./foreignKeys');
const primaryKeys = require('./primaryKeys');
const tables = require('./tables');
const modifications = require('./modifications');
+const loadSqlCode = require('./loadSqlCode');
+const views = require('./views');
+const programmables = require('./programmables');
module.exports = {
columns,
@@ -10,4 +13,7 @@ module.exports = {
foreignKeys,
primaryKeys,
modifications,
+ loadSqlCode,
+ views,
+ programmables,
};
diff --git a/packages/engines/mssql/sql/loadSqlCode.js b/packages/engines/mssql/sql/loadSqlCode.js
new file mode 100644
index 00000000..c5f8ee87
--- /dev/null
+++ b/packages/engines/mssql/sql/loadSqlCode.js
@@ -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
+`;
diff --git a/packages/engines/mssql/sql/programmables.js b/packages/engines/mssql/sql/programmables.js
new file mode 100644
index 00000000..8e651d3e
--- /dev/null
+++ b/packages/engines/mssql/sql/programmables.js
@@ -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]
+`;
diff --git a/packages/engines/mssql/sql/views.js b/packages/engines/mssql/sql/views.js
new file mode 100644
index 00000000..f0edb498
--- /dev/null
+++ b/packages/engines/mssql/sql/views.js
@@ -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]
+`;
diff --git a/packages/web/src/appobj/databaseObjectAppObject.js b/packages/web/src/appobj/databaseObjectAppObject.js
new file mode 100644
index 00000000..69087f7d
--- /dev/null
+++ b/packages/web/src/appobj/databaseObjectAppObject.js
@@ -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;
diff --git a/packages/web/src/appobj/tableAppObject.js b/packages/web/src/appobj/tableAppObject.js
index af159191..a1e69edd 100644
--- a/packages/web/src/appobj/tableAppObject.js
+++ b/packages/web/src/appobj/tableAppObject.js
@@ -58,9 +58,10 @@ const tableAppObject = () => ({ conid, database, pureName, schemaName }, { setOp
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;
diff --git a/packages/web/src/appobj/viewAppObject.js b/packages/web/src/appobj/viewAppObject.js
new file mode 100644
index 00000000..70604317
--- /dev/null
+++ b/packages/web/src/appobj/viewAppObject.js
@@ -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 (
+// <>
+// Open data
+// Open structure
+// Create SQL
+// >
+// );
+// }
+
+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;
diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js
index c4c4fb7c..6ccdf813 100644
--- a/packages/web/src/tabs/QueryTab.js
+++ b/packages/web/src/tabs/QueryTab.js
@@ -57,6 +57,8 @@ export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPo
const handleExecute = async () => {
setExecuteNumber((num) => num + 1);
+ const selectedText = editorRef.current.editor.getSelectedText();
+
let sesid = sessionId;
if (!sesid) {
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', {
sesid,
- sql: queryText,
+ sql: selectedText || queryText,
});
};
@@ -99,7 +101,11 @@ export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPo
/>
-
+
diff --git a/packages/web/src/widgets/DatabaseWidget.js b/packages/web/src/widgets/DatabaseWidget.js
index 63651a16..3dd02f2f 100644
--- a/packages/web/src/widgets/DatabaseWidget.js
+++ b/packages/web/src/widgets/DatabaseWidget.js
@@ -1,5 +1,6 @@
import React from 'react';
import styled from 'styled-components';
+import _ from 'lodash';
import useFetch from '../utility/useFetch';
import { AppObjectList } from '../appobj/AppObjectList';
@@ -9,6 +10,7 @@ import { useSetCurrentDatabase, useCurrentDatabase } from '../utility/globalStat
import tableAppObject from '../appobj/tableAppObject';
import theme from '../theme';
import InlineButton from './InlineButton';
+import databaseObjectAppObject from '../appobj/databaseObjectAppObject';
const SearchBoxWrapper = styled.div`
display: flex;
@@ -46,7 +48,7 @@ const Input = styled.input`
function SubDatabaseList({ data }) {
const setDb = useSetCurrentDatabase();
- const handleDatabaseClick = database => {
+ const handleDatabaseClick = (database) => {
setDb({
...database,
connection: data,
@@ -59,7 +61,7 @@ function SubDatabaseList({ data }) {
});
return (
({ ...db, connection: data }))}
+ list={(databases || []).map((db) => ({ ...db, connection: data }))}
makeAppObj={databaseAppObject({ boldCurrentDatabase: true })}
onObjectClick={handleDatabaseClick}
/>
@@ -75,7 +77,7 @@ function ConnectionList() {
return (
<>
- setFilter(e.target.value)} />
+ setFilter(e.target.value)} />
Refresh
@@ -96,8 +98,13 @@ function SqlObjectList({ conid, database }) {
url: `database-connections/list-objects?conid=${conid}&database=${database}`,
reloadTrigger: `database-structure-changed-${conid}-${database}`,
});
- const { tables } = objects || {};
+
const [filter, setFilter] = React.useState('');
+ const objectList = _.flatten(
+ ['tables', 'views'].map((objectTypeField) =>
+ ((objects || {})[objectTypeField] || []).map((obj) => ({ ...obj, objectTypeField }))
+ )
+ );
return (
<>
@@ -105,15 +112,15 @@ function SqlObjectList({ conid, database }) {
type="text"
placeholder="Search tables or objects"
value={filter}
- onChange={e => setFilter(e.target.value)}
+ onChange={(e) => setFilter(e.target.value)}
/>
Refresh
({ ...x, conid, database }))}
- makeAppObj={tableAppObject()}
- groupFunc={appobj => 'Tables'}
+ list={objectList.map((x) => ({ ...x, conid, database }))}
+ makeAppObj={databaseObjectAppObject()}
+ groupFunc={(appobj) => appobj.groupTitle}
filter={filter}
/>