diff --git a/packages/types/dbinfo.d.ts b/packages/types/dbinfo.d.ts index b5b59d1ec..680bbb236 100644 --- a/packages/types/dbinfo.d.ts +++ b/packages/types/dbinfo.d.ts @@ -35,7 +35,7 @@ export interface IndexInfo extends ColumnsConstraintInfo { isUnique: boolean; // indexType: 'normal' | 'clustered' | 'xml' | 'spatial' | 'fulltext'; indexType?: string; - // condition for filtered index (SQL Server) + // condition for filtered index (SQL Server) filterDefinition?: string; } @@ -118,9 +118,23 @@ export interface ViewInfo extends SqlObjectInfo { columns: ColumnInfo[]; } -export interface ProcedureInfo extends SqlObjectInfo {} +export interface ParameterInfo { + objectId?: string | number; + parentObjectId?: string | number; + pureName: string; + dataType: string; + fullDataType: string; + maxLength?: number; + precision?: number; + scale?: string; + isOutputParameter?: boolean; +} +export interface ProcedureInfo extends SqlObjectInfo { + parameters?: ParameterInfo[]; +} export interface FunctionInfo extends SqlObjectInfo { + parameters?: ParameterInfo[]; // returnDataType?: string; } diff --git a/packages/web/src/appobj/AppObjectListItem.svelte b/packages/web/src/appobj/AppObjectListItem.svelte index 2b61b232b..395127e63 100644 --- a/packages/web/src/appobj/AppObjectListItem.svelte +++ b/packages/web/src/appobj/AppObjectListItem.svelte @@ -62,7 +62,7 @@ {#if (isExpanded || isExpandedBySearch) && subItemsComponent}
+ export const extractKey = ({ columnName }) => columnName; + + + + + diff --git a/packages/web/src/appobj/SubProcedureParamList.svelte b/packages/web/src/appobj/SubProcedureParamList.svelte new file mode 100644 index 000000000..206321594 --- /dev/null +++ b/packages/web/src/appobj/SubProcedureParamList.svelte @@ -0,0 +1,15 @@ + + + ({ + ...data, + ...parameter, + }))} + module={parameterAppObject} +/> diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte index 90cdae967..1f8d6930f 100644 --- a/packages/web/src/icons/FontIcon.svelte +++ b/packages/web/src/icons/FontIcon.svelte @@ -63,6 +63,7 @@ 'icon open-in-new': 'mdi mdi-open-in-new', 'icon add-folder': 'mdi mdi-folder-plus-outline', 'icon add-column': 'mdi mdi-table-column-plus-after', + 'icon parameter': 'mdi mdi-at', 'icon window-restore': 'mdi mdi-window-restore', 'icon window-maximize': 'mdi mdi-window-maximize', diff --git a/packages/web/src/widgets/ConnectionList.svelte b/packages/web/src/widgets/ConnectionList.svelte index 4b84fe07d..61ba93bc3 100644 --- a/packages/web/src/widgets/ConnectionList.svelte +++ b/packages/web/src/widgets/ConnectionList.svelte @@ -251,7 +251,7 @@ SubDatabaseList} expandOnClick isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase} {filter} @@ -275,7 +275,7 @@ SubDatabaseList} expandOnClick isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase} {filter} diff --git a/packages/web/src/widgets/SqlObjectList.svelte b/packages/web/src/widgets/SqlObjectList.svelte index a71d4cbc4..04151597b 100644 --- a/packages/web/src/widgets/SqlObjectList.svelte +++ b/packages/web/src/widgets/SqlObjectList.svelte @@ -52,6 +52,7 @@ import AppObjectListHandler from './AppObjectListHandler.svelte'; import { matchDatabaseObjectAppObject } from '../appobj/appObjectTools'; import FocusedConnectionInfoWidget from './FocusedConnectionInfoWidget.svelte'; + import SubProcedureParamList from '../appobj/SubProcedureParamList.svelte'; export let conid; export let database; @@ -232,9 +233,16 @@ .map(x => ({ ...x, conid, database }))} module={databaseObjectAppObject} groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)} - subItemsComponent={SubColumnParamList} + subItemsComponent={data => + data.objectTypeField == 'procedures' || data.objectTypeField == 'functions' + ? SubProcedureParamList + : SubColumnParamList} isExpandable={data => - data.objectTypeField == 'tables' || data.objectTypeField == 'views' || data.objectTypeField == 'matviews'} + data.objectTypeField == 'tables' || + data.objectTypeField == 'views' || + data.objectTypeField == 'matviews' || + ((data.objectTypeField == 'procedures' || data.objectTypeField == 'functions') && + !!data.parameters?.length)} expandIconFunc={chevronExpandIcon} {filter} passProps={{ diff --git a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js index 1eac0ea32..72b4060f9 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js +++ b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js @@ -31,6 +31,18 @@ function simplifyComutedExpression(expr) { return expr; } +function getFullDataTypeName({ dataType, charMaxLength, numericScale, numericPrecision }) { + let fullDataType = dataType; + if (charMaxLength && isTypeString(dataType)) { + fullDataType = `${dataType}(${charMaxLength < 0 ? 'MAX' : charMaxLength})`; + } + if (numericPrecision && numericScale && isTypeNumeric(dataType)) { + fullDataType = `${dataType}(${numericPrecision},${numericScale})`; + } + + return fullDataType; +} + function getColumnInfo({ isNullable, isIdentity, @@ -43,13 +55,12 @@ function getColumnInfo({ defaultConstraint, computedExpression, }) { - let fullDataType = dataType; - if (charMaxLength && isTypeString(dataType)) { - fullDataType = `${dataType}(${charMaxLength < 0 ? 'MAX' : charMaxLength})`; - } - if (numericPrecision && numericScale && isTypeNumeric(dataType)) { - fullDataType = `${dataType}(${numericPrecision},${numericScale})`; - } + const fullDataType = getFullDataTypeName({ + dataType, + charMaxLength, + numericPrecision, + numericScale, + }); if (defaultValue) { defaultValue = defaultValue.trim(); @@ -116,7 +127,11 @@ class MsSqlAnalyser extends DatabaseAnalyser { this.feedback({ analysingMessage: 'Loading views' }); const viewsRows = await this.analyserQuery('views', ['views']); this.feedback({ analysingMessage: 'Loading procedures & functions' }); + const programmableRows = await this.analyserQuery('programmables', ['procedures', 'functions']); + const prodceureParameterRows = await this.analyserQuery('proceduresParameters'); + const functionParameterRows = await this.analyserQuery('functionParameters'); + this.feedback({ analysingMessage: 'Loading view columns' }); const viewColumnRows = await this.analyserQuery('viewColumns', ['views']); @@ -157,20 +172,46 @@ class MsSqlAnalyser extends DatabaseAnalyser { columns: viewColumnRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo), })); + const prodceureParameter = prodceureParameterRows.rows.map(row => ({ + ...row, + fullDataType: getFullDataTypeName(row), + })); + + const prodceureToParameters = prodceureParameter.reduce((acc, parameter) => { + if (!acc[parameter.parentObjectId]) acc[parameter.parentObjectId] = []; + acc[parameter.parentObjectId].push(parameter); + + return acc; + }, {}); + const procedures = programmableRows.rows .filter(x => x.sqlObjectType.trim() == 'P') .map(row => ({ ...row, contentHash: row.modifyDate && row.modifyDate.toISOString(), createSql: getCreateSql(row), + parameters: prodceureToParameters[row.objectId], })); + const functionParameters = functionParameterRows.rows.map(row => ({ + ...row, + fullDataType: getFullDataTypeName(row), + })); + + const functionToParameters = functionParameters.reduce((acc, parameter) => { + if (!acc[parameter.parentObjectId]) acc[parameter.parentObjectId] = []; + + acc[parameter.parentObjectId].push(parameter); + return acc; + }, {}); + const functions = programmableRows.rows .filter(x => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim())) .map(row => ({ ...row, contentHash: row.modifyDate && row.modifyDate.toISOString(), createSql: getCreateSql(row), + parameters: functionToParameters[row.objectId], })); this.feedback({ analysingMessage: null }); diff --git a/plugins/dbgate-plugin-mssql/src/backend/sql/functionParameters.js b/plugins/dbgate-plugin-mssql/src/backend/sql/functionParameters.js new file mode 100644 index 000000000..f7b40dc72 --- /dev/null +++ b/plugins/dbgate-plugin-mssql/src/backend/sql/functionParameters.js @@ -0,0 +1,26 @@ +module.exports = ` +SELECT + o.object_id as parentObjectId, + p.object_id AS parameterObjectId, + CASE + WHEN p.name IS NULL OR LTRIM(RTRIM(p.name)) = '' THEN + '@Output' + ELSE + p.name + END AS pureName, + TYPE_NAME(p.user_type_id) AS dataType, + p.max_length AS charMaxLength, + p.precision AS precision, + p.scale AS scale, + p.is_output AS isOutputParameter, + p.parameter_id AS parameterIndex +FROM + sys.objects o +JOIN + sys.parameters p ON o.object_id = p.object_id +WHERE + o.type IN ('FN', 'IF', 'TF') +ORDER BY + p.object_id, + p.parameter_id; +`; diff --git a/plugins/dbgate-plugin-mssql/src/backend/sql/index.js b/plugins/dbgate-plugin-mssql/src/backend/sql/index.js index 563b8d080..6352b6a9b 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/sql/index.js +++ b/plugins/dbgate-plugin-mssql/src/backend/sql/index.js @@ -7,6 +7,8 @@ const modifications = require('./modifications'); const loadSqlCode = require('./loadSqlCode'); const views = require('./views'); const programmables = require('./programmables'); +const proceduresParameters = require('./proceduresParameters'); +const functionParameters = require('./functionParameters'); const viewColumns = require('./viewColumns'); const indexes = require('./indexes'); const indexcols = require('./indexcols'); @@ -20,6 +22,8 @@ module.exports = { loadSqlCode, views, programmables, + proceduresParameters, + functionParameters, viewColumns, indexes, indexcols, diff --git a/plugins/dbgate-plugin-mssql/src/backend/sql/proceduresParameters.js b/plugins/dbgate-plugin-mssql/src/backend/sql/proceduresParameters.js new file mode 100644 index 000000000..00c7b2c9f --- /dev/null +++ b/plugins/dbgate-plugin-mssql/src/backend/sql/proceduresParameters.js @@ -0,0 +1,21 @@ +module.exports = ` +SELECT + o.object_id as parentObjectId, + p.object_id as objectId, + p.name AS pureName, + TYPE_NAME(p.user_type_id) AS dataType, + p.max_length AS charMaxLength, + p.precision AS precision, + p.scale AS scale, + p.is_output AS isOutputParameter, + p.parameter_id AS parameterIndex +FROM + sys.objects o +JOIN + sys.parameters p ON o.object_id = p.object_id +WHERE + o.type = 'P' +ORDER BY + o.object_id, + p.parameter_id; +`;