diff --git a/integration-tests/__tests__/object-analyse.spec.js b/integration-tests/__tests__/object-analyse.spec.js
index e49e098df..f94223b69 100644
--- a/integration-tests/__tests__/object-analyse.spec.js
+++ b/integration-tests/__tests__/object-analyse.spec.js
@@ -10,6 +10,14 @@ function flatSource() {
);
}
+function flatSourceParameters() {
+ return _.flatten(
+ engines.map(engine =>
+ (engine.parameters || []).map(parameter => [engine.label, parameter.testName, parameter, engine])
+ )
+ );
+}
+
const obj1Match = expect.objectContaining({
pureName: 'obj1',
});
@@ -78,7 +86,7 @@ describe('Object analyse', () => {
const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2[type].length).toEqual(0);
- await driver.query(conn, structure1[type][0].createSql, { discardResult: true });
+ await driver.script(conn, structure1[type][0].createSql);
const structure3 = await driver.analyseIncremental(conn, structure2);
@@ -86,4 +94,45 @@ describe('Object analyse', () => {
expect(structure3[type][0]).toEqual(type.includes('views') ? view1Match : obj1Match);
})
);
+
+ test.each(flatSourceParameters())(
+ 'Test parameters simple analyse - %s - %s',
+ testWrapper(async (conn, driver, testName, parameter, engine) => {
+ for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
+ for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true });
+
+ await driver.query(conn, parameter.create, { discardResult: true });
+ const structure = await driver.analyseFull(conn);
+
+ const parameters = structure[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
+
+ expect(parameters.length).toEqual(parameter.list.length);
+ for (let i = 0; i < parameters.length; i += 1) {
+ expect(parameters[i]).toEqual(expect.objectContaining(parameter.list[i]));
+ }
+ })
+ );
+
+ test.each(flatSourceParameters())(
+ 'Test parameters create SQL - %s - %s',
+ testWrapper(async (conn, driver, testName, parameter, engine) => {
+ for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
+ for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true });
+
+ await driver.query(conn, parameter.create, { discardResult: true });
+ const structure1 = await driver.analyseFull(conn);
+ await driver.query(conn, parameter.drop, { discardResult: true });
+
+ const obj = structure1[parameter.objectTypeField].find(x => x.pureName == 'obj1');
+ await driver.script(conn, obj.createSql);
+
+ const structure2 = await driver.analyseFull(conn);
+ const parameters = structure2[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
+
+ expect(parameters.length).toEqual(parameter.list.length);
+ for (let i = 0; i < parameters.length; i += 1) {
+ expect(parameters[i]).toEqual(expect.objectContaining(parameter.list[i]));
+ }
+ })
+ );
});
diff --git a/integration-tests/engines.js b/integration-tests/engines.js
index 1bd0ab110..6ea74f7ab 100644
--- a/integration-tests/engines.js
+++ b/integration-tests/engines.js
@@ -28,7 +28,16 @@ const engines = [
port: 15001,
},
// skipOnCI: true,
- objects: [views],
+ objects: [
+ views,
+ {
+ type: 'procedures',
+ create1: 'CREATE PROCEDURE obj1() BEGIN SELECT * FROM t1; END',
+ create2: 'CREATE PROCEDURE obj2() BEGIN SELECT * FROM t2; END',
+ drop1: 'DROP PROCEDURE obj1',
+ drop2: 'DROP PROCEDURE obj2',
+ },
+ ],
dbSnapshotBySeconds: true,
dumpFile: 'data/chinook-mysql.sql',
dumpChecks: [
@@ -37,6 +46,68 @@ const engines = [
res: '25',
},
],
+ parametersOtherSql: ['CREATE PROCEDURE obj2(a int, b int) BEGIN SELECT * FROM t1; END'],
+ parameters: [
+ {
+ testName: 'simple',
+ create: 'CREATE PROCEDURE obj1(a int) BEGIN SELECT * FROM t1; END',
+ drop: 'DROP PROCEDURE obj1',
+ objectTypeField: 'procedures',
+ list: [
+ {
+ parameterName: 'a',
+ parameterMode: 'IN',
+ dataType: 'int',
+ },
+ ],
+ },
+ {
+ testName: 'paramTypes',
+ create: 'CREATE PROCEDURE obj1(a int, b varchar(50), c numeric(10,2)) BEGIN SELECT * FROM t1; END',
+ drop: 'DROP PROCEDURE obj1',
+ objectTypeField: 'procedures',
+ list: [
+ {
+ parameterName: 'a',
+ parameterMode: 'IN',
+ dataType: 'int',
+ },
+ {
+ parameterName: 'b',
+ parameterMode: 'IN',
+ dataType: 'varchar(50)',
+ },
+ {
+ parameterName: 'c',
+ parameterMode: 'IN',
+ dataType: 'decimal(10,2)',
+ },
+ ],
+ },
+ {
+ testName: 'paramModes',
+ create: 'CREATE PROCEDURE obj1(IN a int, OUT b int, INOUT c int) BEGIN SELECT * FROM t1; END',
+ drop: 'DROP PROCEDURE obj1',
+ objectTypeField: 'procedures',
+ list: [
+ {
+ parameterName: 'a',
+ parameterMode: 'IN',
+ dataType: 'int',
+ },
+ {
+ parameterName: 'b',
+ parameterMode: 'OUT',
+ dataType: 'int',
+ },
+ {
+ parameterName: 'c',
+ parameterMode: 'INOUT',
+ dataType: 'int',
+ },
+ ],
+ },
+ ],
},
{
label: 'MariaDB',
@@ -105,6 +176,94 @@ const engines = [
res: '25',
},
],
+
+ parametersOtherSql: ['CREATE PROCEDURE obj2(a integer, b integer) LANGUAGE SQL AS $$ select * from t1 $$'],
+ parameters: [
+ {
+ testName: 'simple',
+ create: 'CREATE PROCEDURE obj1(a integer) LANGUAGE SQL AS $$ select * from t1 $$',
+ drop: 'DROP PROCEDURE obj1',
+ objectTypeField: 'procedures',
+ list: [
+ {
+ parameterName: 'a',
+ parameterMode: 'IN',
+ dataType: 'integer',
+ },
+ ],
+ },
+ {
+ testName: 'dataTypes',
+ create:
+ 'CREATE PROCEDURE obj1(a integer, b varchar(20), c numeric(18,2)) LANGUAGE SQL AS $$ select * from t1 $$',
+ drop: 'DROP PROCEDURE obj1',
+ objectTypeField: 'procedures',
+ list: [
+ {
+ parameterName: 'a',
+ parameterMode: 'IN',
+ dataType: 'integer',
+ },
+ {
+ parameterName: 'b',
+ parameterMode: 'IN',
+ dataType: 'varchar',
+ },
+ {
+ parameterName: 'c',
+ parameterMode: 'IN',
+ dataType: 'numeric',
+ },
+ ],
+ },
+ {
+ testName: 'paramModes',
+ create: 'CREATE PROCEDURE obj1(IN a integer, INOUT b integer) LANGUAGE SQL AS $$ select * from t1 $$',
+ drop: 'DROP PROCEDURE obj1',
+ objectTypeField: 'procedures',
+ list: [
+ {
+ parameterName: 'a',
+ parameterMode: 'IN',
+ dataType: 'integer',
+ },
+ {
+ parameterName: 'b',
+ parameterMode: 'INOUT',
+ dataType: 'integer',
+ },
+ ],
+ },
+ {
+ testName: 'paramModesFunction',
+ objectTypeField: 'functions',
+ create: `
+create or replace function obj1(
+ out min_len int,
+ out max_len int)
+language plpgsql
+as $$
+begin
+ select min(id),
+ max(id)
+ into min_len, max_len
+ from t1;
+end;$$`,
+ drop: 'DROP FUNCTION obj1',
+ list: [
+ {
+ parameterName: 'min_len',
+ parameterMode: 'OUT',
+ dataType: 'integer',
+ },
+ {
+ parameterName: 'max_len',
+ parameterMode: 'OUT',
+ dataType: 'integer',
+ },
+ ],
+ },
+ ],
},
{
label: 'SQL Server',
@@ -129,6 +288,63 @@ const engines = [
drop2: 'DROP PROCEDURE obj2',
},
],
+ parametersOtherSql: ['CREATE PROCEDURE obj2 (@p1 int, @p2 int) AS SELECT id from t1'],
+ parameters: [
+ {
+ testName: 'simple',
+ create: 'CREATE PROCEDURE obj1 (@param1 int) AS SELECT id from t1',
+ drop: 'DROP PROCEDURE obj1',
+ objectTypeField: 'procedures',
+ list: [
+ {
+ parameterName: '@param1',
+ parameterMode: 'IN',
+ dataType: 'int',
+ },
+ ],
+ },
+ {
+ testName: 'dataTypes',
+ create: 'CREATE PROCEDURE obj1 (@p1 bit, @p2 nvarchar(20), @p3 decimal(18,2), @p4 float) AS SELECT id from t1',
+ drop: 'DROP PROCEDURE obj1',
+ objectTypeField: 'procedures',
+ list: [
+ {
+ parameterName: '@p1',
+ parameterMode: 'IN',
+ dataType: 'bit',
+ },
+ {
+ parameterName: '@p2',
+ parameterMode: 'IN',
+ dataType: 'nvarchar(20)',
+ },
+ {
+ parameterName: '@p3',
+ parameterMode: 'IN',
+ dataType: 'decimal(18,2)',
+ },
+ {
+ parameterName: '@p4',
+ parameterMode: 'IN',
+ dataType: 'float',
+ },
+ ],
+ },
+ {
+ testName: 'outputParam',
+ create: 'CREATE PROCEDURE obj1 (@p1 int OUTPUT) AS SELECT id from t1',
+ drop: 'DROP PROCEDURE obj1',
+ objectTypeField: 'procedures',
+ list: [
+ {
+ parameterName: '@p1',
+ parameterMode: 'OUT',
+ dataType: 'int',
+ },
+ ],
+ },
+ ],
supportSchemas: true,
supportRenameSqlObject: true,
defaultSchemaName: 'dbo',
@@ -188,10 +404,10 @@ const engines = [
const filterLocal = [
// filter local testing
- '-MySQL',
+ 'MySQL',
'-MariaDB',
'-PostgreSQL',
- 'SQL Server',
+ '-SQL Server',
'-SQLite',
'-CockroachDB',
'-ClickHouse',
diff --git a/packages/types/dbinfo.d.ts b/packages/types/dbinfo.d.ts
index b5b59d1ec..24d379c61 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,21 @@ export interface ViewInfo extends SqlObjectInfo {
columns: ColumnInfo[];
}
-export interface ProcedureInfo extends SqlObjectInfo {}
+export type ParameterMode = 'IN' | 'OUT' | 'INOUT' | 'RETURN';
+
+export interface ParameterInfo {
+ schemaName: string;
+ parameterName?: string;
+ pureName: string;
+ dataType: string;
+ parameterMode?: ParameterMode;
+}
+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..852775e26
--- /dev/null
+++ b/packages/web/src/appobj/SubProcedureParamList.svelte
@@ -0,0 +1,14 @@
+
+
+ ({
+ ...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 58b161487..d73169be8 100644
--- a/packages/web/src/widgets/ConnectionList.svelte
+++ b/packages/web/src/widgets/ConnectionList.svelte
@@ -274,7 +274,7 @@
SubDatabaseList}
expandOnClick
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
{filter}
@@ -298,7 +298,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 8f38a7efe..aef4486bc 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;
@@ -238,9 +239,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..79b040f76 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 procedureParameterRows = 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 procedureParameter = procedureParameterRows.rows.map(row => ({
+ ...row,
+ dataType: getFullDataTypeName(row),
+ }));
+
+ const prodceureToParameters = procedureParameter.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,
+ dataType: 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..2a255d939
--- /dev/null
+++ b/plugins/dbgate-plugin-mssql/src/backend/sql/functionParameters.js
@@ -0,0 +1,47 @@
+module.exports = `
+SELECT
+ o.object_id as parentObjectId,
+ p.object_id AS parameterObjectId,
+ o.name as pureName,
+ CASE
+ WHEN p.name IS NULL OR LTRIM(RTRIM(p.name)) = '' THEN
+ '@Output'
+ ELSE
+ p.name
+ END AS parameterName,
+ TYPE_NAME(p.user_type_id) AS dataType,
+ CASE
+ WHEN TYPE_NAME(p.user_type_id) = 'nvarchar' THEN p.max_length / 2
+ ELSE p.max_length
+ END AS charMaxLength,
+ CASE
+ WHEN p.is_output = 1 THEN 'OUT'
+ ELSE 'IN'
+ END AS parameterMode,
+ CASE
+ WHEN TYPE_NAME(p.user_type_id) IN ('numeric', 'decimal') THEN p.precision
+ ELSE NULL
+ END AS numericPrecision,
+ CASE
+ WHEN TYPE_NAME(p.user_type_id) IN ('numeric', 'decimal') THEN p.scale
+ ELSE NULL
+ END AS numericScale,
+ CASE
+ WHEN p.is_output = 1 THEN 'OUT'
+ ELSE 'IN'
+ END AS parameterMode,
+ p.parameter_id AS parameterIndex,
+ s.name as schemaName
+FROM
+ sys.objects o
+JOIN
+ sys.parameters p ON o.object_id = p.object_id
+INNER JOIN
+ sys.schemas s ON s.schema_id=o.schema_id
+WHERE
+ o.type IN ('FN', 'IF', 'TF')
+ and o.object_id =OBJECT_ID_CONDITION and s.name =SCHEMA_NAME_CONDITION
+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..d02b5fcb1
--- /dev/null
+++ b/plugins/dbgate-plugin-mssql/src/backend/sql/proceduresParameters.js
@@ -0,0 +1,38 @@
+module.exports = `
+SELECT
+ o.object_id as parentObjectId,
+ p.object_id as objectId,
+ o.name as pureName,
+ p.name AS parameterName,
+ TYPE_NAME(p.user_type_id) AS dataType,
+ CASE
+ WHEN TYPE_NAME(p.user_type_id) = 'nvarchar' THEN p.max_length / 2
+ ELSE p.max_length
+ END AS charMaxLength,
+ CASE
+ WHEN p.is_output = 1 THEN 'OUT'
+ ELSE 'IN'
+ END AS parameterMode,
+ CASE
+ WHEN TYPE_NAME(p.user_type_id) IN ('numeric', 'decimal') THEN p.precision
+ ELSE NULL
+ END AS numericPrecision,
+ CASE
+ WHEN TYPE_NAME(p.user_type_id) IN ('numeric', 'decimal') THEN p.scale
+ ELSE NULL
+ END AS numericScale,
+ p.parameter_id AS parameterIndex,
+ s.name as schemaName
+FROM
+ sys.objects o
+JOIN
+ sys.parameters p ON o.object_id = p.object_id
+INNER JOIN
+ sys.schemas s ON s.schema_id=o.schema_id
+WHERE
+ o.type = 'P'
+ and o.object_id =OBJECT_ID_CONDITION and s.name =SCHEMA_NAME_CONDITION
+ORDER BY
+ o.object_id,
+ p.parameter_id;
+`;
diff --git a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js
index ae68e0ce2..0e4565568 100644
--- a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js
+++ b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js
@@ -15,6 +15,11 @@ function quoteDefaultValue(value) {
return value;
}
+function normalizeTypeName(typeName) {
+ if (/int\(\d+\)/.test(typeName)) return 'int';
+ return typeName;
+}
+
function getColumnInfo(
{
isNullable,
@@ -60,6 +65,18 @@ function getColumnInfo(
};
}
+function getParametersSqlString(parameters = []) {
+ if (!parameters?.length) return '';
+
+ return parameters
+ .map(i => {
+ const mode = i.parameterMode ? `${i.parameterMode} ` : '';
+ const dataType = i.dataType ? ` ${i.dataType.toUpperCase()}` : '';
+ return mode + i.parameterName + dataType;
+ })
+ .join(', ');
+}
+
class Analyser extends DatabaseAnalyser {
constructor(dbhan, driver, version) {
super(dbhan, driver, version);
@@ -114,6 +131,30 @@ class Analyser extends DatabaseAnalyser {
this.feedback({ analysingMessage: 'Loading programmables' });
const programmables = await this.analyserQuery('programmables', ['procedures', 'functions']);
+ const parameters = await this.analyserQuery('parameters', ['procedures', 'functions']);
+
+ const functionParameters = parameters.rows.filter(x => x.routineType == 'FUNCTION');
+ const functionNameToParameters = functionParameters.reduce((acc, row) => {
+ if (!acc[`${row.schemaName}.${row.pureName}`]) acc[`${row.schemaName}.${row.pureName}`] = [];
+
+ acc[`${row.schemaName}.${row.pureName}`].push({
+ ...row,
+ dataType: normalizeTypeName(row.dataType),
+ });
+ return acc;
+ }, {});
+
+ const procedureParameters = parameters.rows.filter(x => x.routineType == 'PROCEDURE');
+ const procedureNameToParameters = procedureParameters.reduce((acc, row) => {
+ if (!acc[`${row.schemaName}.${row.pureName}`]) acc[`${row.schemaName}.${row.pureName}`] = [];
+
+ acc[`${row.schemaName}.${row.pureName}`].push({
+ ...row,
+ dataType: normalizeTypeName(row.dataType),
+ });
+ return acc;
+ }, {});
+
this.feedback({ analysingMessage: 'Loading view texts' });
const viewTexts = await this.getViewTexts(views.rows.map(x => x.pureName));
this.feedback({ analysingMessage: 'Loading indexes' });
@@ -174,20 +215,26 @@ class Analyser extends DatabaseAnalyser {
.map(x => _.omit(x, ['objectType']))
.map(x => ({
...x,
- createSql: `DELIMITER //\n\nCREATE PROCEDURE \`${x.pureName}\`()\n${x.routineDefinition}\n\nDELIMITER ;\n`,
+ createSql: `DELIMITER //\n\nCREATE PROCEDURE \`${x.pureName}\`(${getParametersSqlString(
+ procedureNameToParameters[`${x.schemaName}.${x.pureName}`]
+ )})\n${x.routineDefinition}\n\nDELIMITER ;\n`,
objectId: x.pureName,
contentHash: _.isDate(x.modifyDate) ? x.modifyDate.toISOString() : x.modifyDate,
+ parameters: procedureNameToParameters[`${x.schemaName}.${x.pureName}`],
})),
functions: programmables.rows
.filter(x => x.objectType == 'FUNCTION')
.map(x => _.omit(x, ['objectType']))
.map(x => ({
...x,
- createSql: `CREATE FUNCTION \`${x.pureName}\`()\nRETURNS ${x.returnDataType} ${
- x.isDeterministic == 'YES' ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'
- }\n${x.routineDefinition}`,
+ createSql: `CREATE FUNCTION \`${x.pureName}\`(${getParametersSqlString(
+ functionNameToParameters[`${x.schemaName}.${x.pureName}`]?.filter(i => i.parameterMode !== 'RETURN')
+ )})\nRETURNS ${x.returnDataType} ${x.isDeterministic == 'YES' ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}\n${
+ x.routineDefinition
+ }`,
objectId: x.pureName,
contentHash: _.isDate(x.modifyDate) ? x.modifyDate.toISOString() : x.modifyDate,
+ parameters: functionNameToParameters[`${x.schemaName}.${x.pureName}`],
})),
};
this.feedback({ analysingMessage: null });
diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/index.js b/plugins/dbgate-plugin-mysql/src/backend/sql/index.js
index 4839071e7..8c3e095fa 100644
--- a/plugins/dbgate-plugin-mysql/src/backend/sql/index.js
+++ b/plugins/dbgate-plugin-mysql/src/backend/sql/index.js
@@ -10,6 +10,7 @@ const procedureModifications = require('./procedureModifications');
const functionModifications = require('./functionModifications');
const uniqueNames = require('./uniqueNames');
const viewTexts = require('./viewTexts');
+const parameters = require('./parameters');
module.exports = {
columns,
@@ -19,6 +20,7 @@ module.exports = {
tableModifications,
views,
programmables,
+ parameters,
procedureModifications,
functionModifications,
indexes,
diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/parameters.js b/plugins/dbgate-plugin-mysql/src/backend/sql/parameters.js
new file mode 100644
index 000000000..e3091da1d
--- /dev/null
+++ b/plugins/dbgate-plugin-mysql/src/backend/sql/parameters.js
@@ -0,0 +1,26 @@
+module.exports = `
+SELECT
+ r.ROUTINE_SCHEMA AS schemaName,
+ r.SPECIFIC_NAME AS pureName,
+ CASE
+ WHEN COALESCE(NULLIF(PARAMETER_MODE, ''), 'RETURN') = 'RETURN' THEN 'Return'
+ ELSE PARAMETER_NAME
+ END AS parameterName,
+ p.CHARACTER_MAXIMUM_LENGTH AS charMaxLength,
+ p.NUMERIC_PRECISION AS numericPrecision,
+ p.NUMERIC_SCALE AS numericScale,
+ p.DTD_IDENTIFIER AS dataType,
+ COALESCE(NULLIF(PARAMETER_MODE, ''), 'RETURN') AS parameterMode,
+ r.ROUTINE_TYPE AS routineType, -- Function or Procedure
+ p.ORDINAL_POSITION AS ordinalPosition
+FROM
+ information_schema.PARAMETERS p
+JOIN
+ information_schema.ROUTINES r
+ON
+ p.SPECIFIC_NAME = r.SPECIFIC_NAME AND r.ROUTINE_SCHEMA = p.SPECIFIC_SCHEMA
+WHERE
+ r.ROUTINE_SCHEMA = '#DATABASE#' AND r.ROUTINE_NAME =OBJECT_ID_CONDITION
+ORDER BY
+ r.ROUTINE_SCHEMA, r.SPECIFIC_NAME, p.ORDINAL_POSITION
+`;
diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/programmables.js b/plugins/dbgate-plugin-mysql/src/backend/sql/programmables.js
index f6442fcc1..588c04012 100644
--- a/plugins/dbgate-plugin-mysql/src/backend/sql/programmables.js
+++ b/plugins/dbgate-plugin-mysql/src/backend/sql/programmables.js
@@ -1,5 +1,6 @@
module.exports = `
select
+ ROUTINE_SCHEMA AS schemaName,
ROUTINE_NAME as pureName,
ROUTINE_TYPE as objectType,
COALESCE(LAST_ALTERED, CREATED) as modifyDate,
diff --git a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js
index 84b67a858..90bb94ab8 100644
--- a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js
+++ b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js
@@ -49,6 +49,19 @@ function getColumnInfo(
};
}
+function getParametersSqlString(parameters = []) {
+ if (!parameters?.length) return '';
+
+ return parameters
+ .map(i => {
+ const mode = i.parameterMode ? `${i.parameterMode} ` : '';
+ const dataType = i.dataType ? ` ${i.dataType.toUpperCase()}` : '';
+ const parameterName = i.parameterName ?? '';
+ return `${mode}${parameterName}${dataType}`;
+ })
+ .join(', ');
+}
+
class Analyser extends DatabaseAnalyser {
constructor(dbhan, driver, version) {
super(dbhan, driver, version);
@@ -144,6 +157,9 @@ class Analyser extends DatabaseAnalyser {
this.feedback({ analysingMessage: 'Loading routines' });
const routines = await this.analyserQuery('routines', ['procedures', 'functions']);
+ this.feedback({ analysingMessage: 'Loading routine parameters' });
+ const routineParametersRows = await this.analyserQuery('proceduresParameters');
+
this.feedback({ analysingMessage: 'Loading indexes' });
const indexes = this.driver.__analyserInternals.skipIndexes
? { rows: [] }
@@ -191,6 +207,40 @@ class Analyser extends DatabaseAnalyser {
columnName: x.column_name,
}));
+ const procedureParameters = routineParametersRows.rows
+ .filter(i => i.routine_type == 'PROCEDURE')
+ .map(i => ({
+ pureName: i.pure_name,
+ parameterName: i.parameter_name,
+ dataType: normalizeTypeName(i.data_type),
+ parameterMode: i.parameter_mode,
+ schemaName: i.schema_name,
+ }));
+
+ const procedureNameToParameters = procedureParameters.reduce((acc, row) => {
+ if (!acc[`${row.schemaName}.${row.pureName}`]) acc[`${row.schemaName}.${row.pureName}`] = [];
+ acc[`${row.schemaName}.${row.pureName}`].push(row);
+
+ return acc;
+ }, {});
+
+ const functionParameters = routineParametersRows.rows
+ .filter(i => i.routine_type == 'FUNCTION')
+ .map(i => ({
+ pureName: i.pure_name,
+ parameterName: i.parameter_name,
+ dataType: normalizeTypeName(i.data_type),
+ parameterMode: i.parameter_mode,
+ schemaName: i.schema_name,
+ }));
+
+ const functionNameToParameters = functionParameters.reduce((acc, row) => {
+ if (!acc[`${row.schemaName}.${row.pureName}`]) acc[`${row.schemaName}.${row.pureName}`] = [];
+ acc[`${row.schemaName}.${row.pureName}`].push(row);
+
+ return acc;
+ }, {});
+
const res = {
tables: tables.rows.map(table => {
const newTable = {
@@ -279,17 +329,23 @@ class Analyser extends DatabaseAnalyser {
objectId: `procedures:${proc.schema_name}.${proc.pure_name}`,
pureName: proc.pure_name,
schemaName: proc.schema_name,
- createSql: `CREATE PROCEDURE "${proc.schema_name}"."${proc.pure_name}"() LANGUAGE ${proc.language}\nAS\n$$\n${proc.definition}\n$$`,
+ createSql: `CREATE PROCEDURE "${proc.schema_name}"."${proc.pure_name}"(${getParametersSqlString(
+ procedureNameToParameters[`${proc.schema_name}.${proc.pure_name}`]
+ )}) LANGUAGE ${proc.language}\nAS\n$$\n${proc.definition}\n$$`,
contentHash: proc.hash_code,
+ parameters: procedureNameToParameters[`${proc.schema_name}.${proc.pure_name}`],
})),
functions: routines.rows
.filter(x => x.object_type == 'FUNCTION')
.map(func => ({
objectId: `functions:${func.schema_name}.${func.pure_name}`,
- createSql: `CREATE FUNCTION "${func.schema_name}"."${func.pure_name}"() RETURNS ${func.data_type} LANGUAGE ${func.language}\nAS\n$$\n${func.definition}\n$$`,
+ createSql: `CREATE FUNCTION "${func.schema_name}"."${func.pure_name}"(${getParametersSqlString(
+ functionNameToParameters[`${func.schema_name}.${func.pure_name}`]
+ )}) RETURNS ${func.data_type.toUpperCase()} LANGUAGE ${func.language}\nAS\n$$\n${func.definition}\n$$`,
pureName: func.pure_name,
schemaName: func.schema_name,
contentHash: func.hash_code,
+ parameters: functionNameToParameters[`${func.schema_name}.${func.pure_name}`],
})),
};
diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/index.js b/plugins/dbgate-plugin-postgres/src/backend/sql/index.js
index 53a858ab5..b3f338646 100644
--- a/plugins/dbgate-plugin-postgres/src/backend/sql/index.js
+++ b/plugins/dbgate-plugin-postgres/src/backend/sql/index.js
@@ -14,6 +14,7 @@ const indexcols = require('./indexcols');
const uniqueNames = require('./uniqueNames');
const geometryColumns = require('./geometryColumns');
const geographyColumns = require('./geographyColumns');
+const proceduresParameters = require('./proceduresParameters');
const fk_keyColumnUsage = require('./fk_key_column_usage');
const fk_referentialConstraints = require('./fk_referential_constraints');
@@ -39,4 +40,5 @@ module.exports = {
uniqueNames,
geometryColumns,
geographyColumns,
+ proceduresParameters,
};
diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/proceduresParameters.js b/plugins/dbgate-plugin-postgres/src/backend/sql/proceduresParameters.js
new file mode 100644
index 000000000..667cfed09
--- /dev/null
+++ b/plugins/dbgate-plugin-postgres/src/backend/sql/proceduresParameters.js
@@ -0,0 +1,31 @@
+module.exports = `
+SELECT
+ proc.specific_schema AS schema_name,
+ proc.routine_name AS pure_name,
+ proc.routine_type as routine_type,
+ args.parameter_name AS parameter_name,
+ args.parameter_mode,
+ args.data_type AS data_type,
+ args.ordinal_position AS parameter_index,
+ args.parameter_mode AS parameter_mode
+FROM
+ information_schema.routines proc
+LEFT JOIN
+ information_schema.parameters args
+ ON proc.specific_schema = args.specific_schema
+ AND proc.specific_name = args.specific_name
+WHERE
+ proc.specific_schema NOT IN ('pg_catalog', 'information_schema') -- Exclude system schemas
+ AND args.parameter_name IS NOT NULL
+ AND proc.routine_type IN ('PROCEDURE', 'FUNCTION') -- Filter for procedures
+ AND proc.specific_schema !~ '^_timescaledb_'
+ AND proc.specific_schema =SCHEMA_NAME_CONDITION
+ AND (
+ (routine_type = 'PROCEDURE' AND ('procedures:' || proc.specific_schema || '.' || routine_name) =OBJECT_ID_CONDITION)
+ OR
+ (routine_type = 'FUNCTION' AND ('functions:' || proc.specific_schema || '.' || routine_name) =OBJECT_ID_CONDITION)
+ )
+ORDER BY
+ schema_name,
+ args.ordinal_position;
+`;