Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e4eb39a19 | |||
| b6399c8271 | |||
| 27188eb2c5 | |||
| 57a997adc3 | |||
| a72a03cc3a | |||
| bb185d9e9f | |||
| 0298660714 | |||
| 1a4009a6b2 | |||
| e40357c052 | |||
| 222ea07cf2 | |||
| 81cea4c0f2 | |||
| 21702f1593 | |||
| 32ddb9c4c7 | |||
| 10fc62ceb7 | |||
| d3018a3136 | |||
| 5c7d2bfd85 | |||
| 0eca5dd95d | |||
| 352e426e17 | |||
| 666122f265 | |||
| a66dc03b99 | |||
| cf5ecb3150 | |||
| 46c365c5cd | |||
| ea76751e4a | |||
| 0bef3f8e71 | |||
| c26c9fae12 | |||
| 446c615bb8 | |||
| c516873541 | |||
| d4326de087 | |||
| eca966bb90 | |||
| 262b4732e3 | |||
| 7f9a30f568 | |||
| a89d2e1365 | |||
| fcd6f6c8fc | |||
| 8313d7f9f1 | |||
| 3a12601103 | |||
| 926949dc89 | |||
| 6b155083ef | |||
| 2b2ecac3ab |
+1
-1
@@ -108,7 +108,7 @@ function commandItem(item) {
|
||||
}
|
||||
|
||||
function buildMenu() {
|
||||
let template = _cloneDeepWith(mainMenuDefinition({ editMenu: true, isMac: isMac() }), item => {
|
||||
let template = _cloneDeepWith(mainMenuDefinition({ editMenu: true }), item => {
|
||||
if (item.divider) {
|
||||
return { type: 'separator' };
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = ({ editMenu, isMac }) => [
|
||||
module.exports = ({ editMenu }) => [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
@@ -24,6 +24,20 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
{ command: 'app.disconnect', hideDisabled: true, skipInApp: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Window',
|
||||
submenu: [
|
||||
{ command: 'tabs.closeTab', hideDisabled: false },
|
||||
{ command: 'tabs.closeAll', hideDisabled: false },
|
||||
{ command: 'tabs.closeTabsWithCurrentDb', hideDisabled: false },
|
||||
{ command: 'tabs.closeTabsButCurrentDb', hideDisabled: false },
|
||||
{ divider: true },
|
||||
{ command: 'app.zoomIn', hideDisabled: true },
|
||||
{ command: 'app.zoomOut', hideDisabled: true },
|
||||
{ command: 'app.zoomReset', hideDisabled: true },
|
||||
],
|
||||
},
|
||||
|
||||
editMenu
|
||||
? {
|
||||
label: 'Edit',
|
||||
@@ -61,15 +75,6 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
{ divider: true },
|
||||
{ command: 'theme.changeTheme', hideDisabled: true },
|
||||
{ command: 'settings.show' },
|
||||
{ divider: true },
|
||||
{ command: 'tabs.closeTab', hideDisabled: false },
|
||||
{ command: 'tabs.closeAll', hideDisabled: false },
|
||||
{ command: 'tabs.closeTabsWithCurrentDb', hideDisabled: false },
|
||||
{ command: 'tabs.closeTabsButCurrentDb', hideDisabled: false },
|
||||
{ divider: true },
|
||||
{ command: 'app.zoomIn', hideDisabled: true },
|
||||
{ command: 'app.zoomOut', hideDisabled: true },
|
||||
{ command: 'app.zoomReset', hideDisabled: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -89,14 +94,6 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
{ command: 'app.resetSettings', hideDisabled: true },
|
||||
],
|
||||
},
|
||||
...(isMac
|
||||
? [
|
||||
{
|
||||
role: 'window',
|
||||
submenu: [{ role: 'minimize' }, { role: 'zoom' }, { type: 'separator' }, { role: 'front' }],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -62,8 +62,6 @@ module.exports = {
|
||||
const logoutUrl = storageConnectionError ? null : await authProvider.getLogoutUrl();
|
||||
const adminConfig = storageConnectionError ? null : await storage.readConfig({ group: 'admin' });
|
||||
|
||||
storage.startRefreshLicense();
|
||||
|
||||
const isAdminPasswordMissing = !!(
|
||||
process.env.STORAGE_DATABASE &&
|
||||
!process.env.ADMIN_PASSWORD &&
|
||||
@@ -83,7 +81,7 @@ module.exports = {
|
||||
isElectron: platformInfo.isElectron,
|
||||
isLicenseValid,
|
||||
isLicenseExpired: checkedLicense?.isExpired,
|
||||
trialDaysLeft: checkedLicense?.licenseTypeObj?.isTrial && !checkedLicense?.isExpired ? checkedLicense?.daysLeft : null,
|
||||
trialDaysLeft: checkedLicense?.isGeneratedTrial && !checkedLicense?.isExpired ? checkedLicense?.daysLeft : null,
|
||||
checkedLicense,
|
||||
configurationError,
|
||||
logoutUrl,
|
||||
|
||||
@@ -26,6 +26,4 @@ module.exports = {
|
||||
async readConfig({ group }) {
|
||||
return {};
|
||||
},
|
||||
|
||||
startRefreshLicense() {},
|
||||
};
|
||||
|
||||
Vendored
+14
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,6 @@
|
||||
}
|
||||
|
||||
// $: console.log('CONFIG', $config);
|
||||
|
||||
$: {
|
||||
if ($config?.isLicenseValid) {
|
||||
internalRedirectTo('/');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormProviderCore {values}>
|
||||
@@ -94,18 +88,9 @@
|
||||
<FormStyledButton
|
||||
value="Purchase DbGate Premium"
|
||||
on:click={async e => {
|
||||
// openWebLink(
|
||||
// `https://auth.dbgate.eu/create-checkout-session-simple?source=trial-${isExpired ? 'expired' : (trialDaysLeft ?? 'no')}`
|
||||
// );
|
||||
|
||||
// openWebLink(
|
||||
// `https://auth-proxy.dbgate.udolni.net/redirect-to-purchase?product=${getElectron() ? 'premium' : 'teram-premium'}&source=trial-${isExpired ? 'expired' : (trialDaysLeft ?? 'no')}`
|
||||
// );
|
||||
|
||||
openWebLink(
|
||||
`https://auth.dbgate.eu/redirect-to-purchase?product=${getElectron() ? 'premium' : 'teram-premium'}&source=trial-${isExpired ? 'expired' : (trialDaysLeft ?? 'no')}`
|
||||
`https://auth.dbgate.eu/create-checkout-session-simple?source=trial-${isExpired ? 'expired' : (trialDaysLeft ?? 'no')}`
|
||||
);
|
||||
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
{#if (isExpanded || isExpandedBySearch) && subItemsComponent}
|
||||
<div class="subitems">
|
||||
<svelte:component
|
||||
this={subItemsComponent}
|
||||
this={subItemsComponent(data)}
|
||||
{data}
|
||||
{filter}
|
||||
{passProps}
|
||||
|
||||
@@ -261,18 +261,6 @@
|
||||
};
|
||||
|
||||
return [
|
||||
!data.singleDatabase && [
|
||||
!$openedConnections.includes(data._id) && {
|
||||
text: 'Connect',
|
||||
onClick: handleConnect,
|
||||
isBold: true,
|
||||
},
|
||||
$openedConnections.includes(data._id) && {
|
||||
text: 'Disconnect',
|
||||
onClick: handleDisconnect,
|
||||
},
|
||||
],
|
||||
{ divider: true },
|
||||
config.runAsPortal == false &&
|
||||
!config.storageDatabase && [
|
||||
{
|
||||
@@ -288,14 +276,21 @@
|
||||
onClick: handleDuplicate,
|
||||
},
|
||||
],
|
||||
{ divider: true },
|
||||
!data.singleDatabase && [
|
||||
!$openedConnections.includes(data._id) && {
|
||||
text: 'Connect',
|
||||
onClick: handleConnect,
|
||||
},
|
||||
hasPermission(`dbops/query`) && { onClick: handleNewQuery, text: 'New query', isNewQuery: true },
|
||||
$openedConnections.includes(data._id) &&
|
||||
data.status && {
|
||||
text: 'Refresh',
|
||||
onClick: handleRefresh,
|
||||
},
|
||||
$openedConnections.includes(data._id) && {
|
||||
text: 'Disconnect',
|
||||
onClick: handleDisconnect,
|
||||
},
|
||||
hasPermission(`dbops/createdb`) &&
|
||||
$openedConnections.includes(data._id) &&
|
||||
driver?.supportedCreateDatabase &&
|
||||
|
||||
@@ -351,8 +351,8 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
|
||||
text: `New ${driver?.collectionSingularLabel ?? 'collection/container'}`,
|
||||
},
|
||||
hasPermission(`dbops/query`) &&
|
||||
driver?.databaseEngineTypes?.includes('sql') && isProApp() && { onClick: handleQueryDesigner, text: 'Design query' },
|
||||
driver?.databaseEngineTypes?.includes('sql') && isProApp() && {
|
||||
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleQueryDesigner, text: 'Design query' },
|
||||
driver?.databaseEngineTypes?.includes('sql') && {
|
||||
onClick: handleNewPerspective,
|
||||
text: 'Design perspective query',
|
||||
},
|
||||
|
||||
@@ -76,12 +76,12 @@
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
isProApp() && {
|
||||
{
|
||||
label: 'Design query',
|
||||
isQueryDesigner: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
isProApp() && {
|
||||
{
|
||||
label: 'Design perspective query',
|
||||
tab: 'PerspectiveTab',
|
||||
forceNewTab: true,
|
||||
@@ -170,11 +170,11 @@
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
isProApp() && {
|
||||
{
|
||||
label: 'Design query',
|
||||
isQueryDesigner: true,
|
||||
},
|
||||
isProApp() && {
|
||||
{
|
||||
label: 'Design perspective query',
|
||||
tab: 'PerspectiveTab',
|
||||
forceNewTab: true,
|
||||
@@ -362,7 +362,7 @@
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
isProApp() && {
|
||||
{
|
||||
label: 'Design perspective query',
|
||||
tab: 'PerspectiveTab',
|
||||
forceNewTab: true,
|
||||
@@ -917,7 +917,6 @@
|
||||
import { defaultDatabaseObjectAppObjectActions, matchDatabaseObjectAppObject } from './appObjectTools';
|
||||
import { getSupportedScriptTemplates } from '../utility/applyScriptTemplate';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" context="module">
|
||||
export const extractKey = ({ columnName }) => columnName;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<AppObjectCore
|
||||
{...$$restProps}
|
||||
{data}
|
||||
title={data.parameterName}
|
||||
extInfo={data.parameterMode && data.parameterMode !== 'IN' ? `${data.dataType} ${data.parameterMode}` : data.dataType}
|
||||
icon={'icon parameter'}
|
||||
disableHover
|
||||
/>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import AppObjectList from './AppObjectList.svelte';
|
||||
import * as parameterAppObject from './ParameterAppObject.svelte';
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<AppObjectList
|
||||
list={(data.parameters || []).map(parameter => ({
|
||||
...data,
|
||||
...parameter,
|
||||
}))}
|
||||
module={parameterAppObject}
|
||||
/>
|
||||
@@ -15,16 +15,16 @@ function getTableLikeActions(dataTab) {
|
||||
tab: dataTab,
|
||||
defaultActionId: 'openTable',
|
||||
},
|
||||
// {
|
||||
// label: 'Open form',
|
||||
// tab: dataTab,
|
||||
// initialData: {
|
||||
// grid: {
|
||||
// isFormView: true,
|
||||
// },
|
||||
// },
|
||||
// defaultActionId: 'openForm',
|
||||
// },
|
||||
{
|
||||
label: 'Open form',
|
||||
tab: dataTab,
|
||||
initialData: {
|
||||
grid: {
|
||||
isFormView: true,
|
||||
},
|
||||
},
|
||||
defaultActionId: 'openForm',
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
display: flex;
|
||||
margin-right: 10px;
|
||||
align-items: center;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.icon {
|
||||
font-size: 20pt;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
<script lang="ts" context="module">
|
||||
const getCurrentEditor = () => getActiveComponent('PerspectiveTab');
|
||||
|
||||
registerCommand({
|
||||
id: 'perspective.refresh',
|
||||
category: 'Perspective',
|
||||
name: 'Refresh',
|
||||
keyText: 'F5 | CtrlOrCommand+R',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
icon: 'icon reload',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().refresh(),
|
||||
});
|
||||
|
||||
registerFileCommands({
|
||||
idPrefix: 'perspective',
|
||||
category: 'Perspective',
|
||||
getCurrentEditor,
|
||||
folder: 'perspectives',
|
||||
format: 'json',
|
||||
fileExtension: 'perspective',
|
||||
|
||||
undoRedo: true,
|
||||
});
|
||||
|
||||
export const allowAddToFavorites = props => true;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { createPerspectiveConfig, PerspectiveCache } from 'dbgate-datalib';
|
||||
|
||||
import PerspectiveView from '../perspectives/PerspectiveView.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
||||
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { useConnectionInfo } from '../utility/metadataLoaders';
|
||||
import { extensions } from '../stores';
|
||||
import invalidateCommands from '../commands/invalidateCommands';
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import createUndoReducer from '../utility/createUndoReducer';
|
||||
import { registerFileCommands } from '../commands/stdCommands';
|
||||
import _ from 'lodash';
|
||||
import ToolStripSaveButton from '../buttons/ToolStripSaveButton.svelte';
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
export let database;
|
||||
export let schemaName;
|
||||
export let pureName;
|
||||
|
||||
let isFormatError;
|
||||
|
||||
export const activator = createActivator('PerspectiveTab', true);
|
||||
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
$: driver = findEngineDriver($connection, $extensions);
|
||||
|
||||
$: setEditorData($modelState.value);
|
||||
|
||||
export function getTabId() {
|
||||
return tabid;
|
||||
}
|
||||
|
||||
export function getData() {
|
||||
return $editorState.value || '';
|
||||
}
|
||||
|
||||
export function canUndo() {
|
||||
return $modelState.canUndo;
|
||||
}
|
||||
|
||||
export function undo() {
|
||||
dispatchModel({ type: 'undo' });
|
||||
invalidateCommands();
|
||||
}
|
||||
|
||||
export function canRedo() {
|
||||
return $modelState.canRedo;
|
||||
}
|
||||
|
||||
export function redo() {
|
||||
dispatchModel({ type: 'redo' });
|
||||
invalidateCommands();
|
||||
}
|
||||
|
||||
const { editorState, editorValue, setEditorData } = useEditorData({
|
||||
tabid,
|
||||
onInitialData: value => {
|
||||
if (!value.nodes) {
|
||||
isFormatError = true;
|
||||
} else {
|
||||
dispatchModel({ type: 'reset', value });
|
||||
}
|
||||
invalidateCommands();
|
||||
},
|
||||
});
|
||||
|
||||
const [modelState, dispatchModel] = createUndoReducer(
|
||||
createPerspectiveConfig(
|
||||
pureName
|
||||
? {
|
||||
schemaName,
|
||||
pureName,
|
||||
}
|
||||
: null
|
||||
)
|
||||
);
|
||||
|
||||
let cache = new PerspectiveCache();
|
||||
const loadedCounts = writable({});
|
||||
|
||||
export function refresh() {
|
||||
cache = new PerspectiveCache();
|
||||
// cache.clear();
|
||||
loadedCounts.set({});
|
||||
}
|
||||
</script>
|
||||
|
||||
<ToolStripContainer>
|
||||
{#if isFormatError}
|
||||
<ErrorInfo message="Invalid perspective format, please create new perspective" alignTop />
|
||||
{:else}
|
||||
<PerspectiveView
|
||||
{conid}
|
||||
{database}
|
||||
{driver}
|
||||
config={$modelState.value}
|
||||
setConfig={(value, reload) => {
|
||||
if (reload) {
|
||||
cache.clear();
|
||||
}
|
||||
dispatchModel({
|
||||
type: 'compute',
|
||||
// useMerge: skipUndoChain,
|
||||
compute: v => (_.isFunction(value) ? value(v) : value),
|
||||
});
|
||||
invalidateCommands();
|
||||
|
||||
// config.update(value);
|
||||
// loadedCounts.set({});
|
||||
}}
|
||||
{cache}
|
||||
{loadedCounts}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="toolstrip">
|
||||
<ToolStripCommandButton
|
||||
command="designer.arrange"
|
||||
buttonLabel={$modelState.value?.isArranged ? '(Arranged)' : 'Arrange'}
|
||||
/>
|
||||
<ToolStripCommandButton command="perspective.refresh" />
|
||||
<ToolStripCommandButton command="perspective.customJoin" />
|
||||
<ToolStripSaveButton idPrefix="perspective" />
|
||||
<ToolStripCommandButton command="perspective.undo" />
|
||||
<ToolStripCommandButton command="perspective.redo" />
|
||||
</svelte:fragment>
|
||||
</ToolStripContainer>
|
||||
@@ -0,0 +1,324 @@
|
||||
<script lang="ts" context="module">
|
||||
const getCurrentEditor = () => getActiveComponent('QueryDesignTab');
|
||||
|
||||
registerCommand({
|
||||
id: 'designer.openSql',
|
||||
category: 'Designer',
|
||||
icon: 'icon sql-file',
|
||||
name: 'Open SQL',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().openSql(),
|
||||
});
|
||||
|
||||
registerFileCommands({
|
||||
idPrefix: 'designer',
|
||||
category: 'Designer',
|
||||
getCurrentEditor,
|
||||
folder: 'query',
|
||||
format: 'json',
|
||||
fileExtension: 'qdesign',
|
||||
|
||||
execute: true,
|
||||
undoRedo: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import sqlFormatter from 'sql-formatter';
|
||||
|
||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||
import SqlEditor from '../query/SqlEditor.svelte';
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import { extensions } from '../stores';
|
||||
import { changeTab } from '../utility/common';
|
||||
import { useConnectionInfo } from '../utility/metadataLoaders';
|
||||
import SocketMessageView from '../query/SocketMessageView.svelte';
|
||||
import useEffect from '../utility/useEffect';
|
||||
import ResultTabs from '../query/ResultTabs.svelte';
|
||||
import { registerFileCommands } from '../commands/stdCommands';
|
||||
import invalidateCommands from '../commands/invalidateCommands';
|
||||
import QueryDesigner from '../designer/QueryDesigner.svelte';
|
||||
import createUndoReducer from '../utility/createUndoReducer';
|
||||
import _ from 'lodash';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { generateDesignedQuery } from '../designer/designerTools';
|
||||
import QueryDesignColumns from '../elements/QueryDesignColumns.svelte';
|
||||
import useTimerLabel from '../utility/useTimerLabel';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import { apiCall, apiOff, apiOn } from '../utility/api';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import newQuery from '../query/newQuery';
|
||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
||||
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
||||
import ToolStripExportButton, { createQuickExportHandlerRef } from '../buttons/ToolStripExportButton.svelte';
|
||||
import ToolStripSaveButton from '../buttons/ToolStripSaveButton.svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
export let database;
|
||||
export let initialArgs;
|
||||
|
||||
const timerLabel = useTimerLabel();
|
||||
|
||||
let busy = false;
|
||||
let executeNumber = 0;
|
||||
let visibleResultTabs = false;
|
||||
let sessionId = null;
|
||||
let sqlPreview = '';
|
||||
|
||||
export const activator = createActivator('QueryDesignTab', true);
|
||||
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
$: engine = findEngineDriver($connection, $extensions);
|
||||
|
||||
$: effect = useEffect(() => {
|
||||
return onSession(sessionId);
|
||||
});
|
||||
function onSession(sid) {
|
||||
if (sid) {
|
||||
apiOn(`session-done-${sid}`, handleSessionDone);
|
||||
apiOn(`session-closed-${sid}`, handleSessionClosed);
|
||||
return () => {
|
||||
apiOff(`session-done-${sid}`, handleSessionDone);
|
||||
apiOff(`session-closed-${sid}`, handleSessionClosed);
|
||||
};
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
$: $effect;
|
||||
|
||||
$: {
|
||||
changeTab(tabid, tab => ({ ...tab, busy }));
|
||||
}
|
||||
|
||||
$: {
|
||||
busy;
|
||||
sessionId;
|
||||
$modelState;
|
||||
invalidateCommands();
|
||||
}
|
||||
|
||||
$: setEditorData($modelState.value);
|
||||
|
||||
$: generatePreview($modelState.value, engine);
|
||||
|
||||
let intervalId;
|
||||
|
||||
onMount(() => {
|
||||
intervalId = setInterval(() => {
|
||||
if (sessionId) {
|
||||
apiCall('sessions/ping', {
|
||||
sesid: sessionId,
|
||||
});
|
||||
}
|
||||
}, 15 * 1000);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(intervalId);
|
||||
});
|
||||
|
||||
export function canKill() {
|
||||
return !!sessionId;
|
||||
}
|
||||
|
||||
export function isBusy() {
|
||||
return busy;
|
||||
}
|
||||
|
||||
export function getTabId() {
|
||||
return tabid;
|
||||
}
|
||||
|
||||
export async function execute() {
|
||||
if (busy) return;
|
||||
executeNumber++;
|
||||
visibleResultTabs = true;
|
||||
|
||||
let sesid = sessionId;
|
||||
if (!sesid) {
|
||||
const resp = await apiCall('sessions/create', {
|
||||
conid,
|
||||
database,
|
||||
});
|
||||
sesid = resp.sesid;
|
||||
sessionId = sesid;
|
||||
}
|
||||
busy = true;
|
||||
timerLabel.start();
|
||||
await apiCall('sessions/execute-query', {
|
||||
sesid,
|
||||
sql: sqlPreview,
|
||||
});
|
||||
}
|
||||
|
||||
export async function kill() {
|
||||
await apiCall('sessions/kill', {
|
||||
sesid: sessionId,
|
||||
});
|
||||
sessionId = null;
|
||||
busy = false;
|
||||
timerLabel.stop();
|
||||
}
|
||||
|
||||
export function getData() {
|
||||
return $editorState.value || '';
|
||||
}
|
||||
|
||||
export function canUndo() {
|
||||
return $modelState.canUndo;
|
||||
}
|
||||
|
||||
export function undo() {
|
||||
dispatchModel({ type: 'undo' });
|
||||
}
|
||||
|
||||
export function canRedo() {
|
||||
return $modelState.canRedo;
|
||||
}
|
||||
|
||||
export function redo() {
|
||||
dispatchModel({ type: 'redo' });
|
||||
}
|
||||
|
||||
export function openSql() {
|
||||
newQuery({ initialData: sqlPreview });
|
||||
}
|
||||
|
||||
const generatePreview = (value, engine) => {
|
||||
if (!engine || !value) return;
|
||||
const sql = generateDesignedQuery(value, engine);
|
||||
sqlPreview = sqlFormatter.format(sql);
|
||||
};
|
||||
|
||||
const handleSessionDone = () => {
|
||||
busy = false;
|
||||
timerLabel.stop();
|
||||
};
|
||||
|
||||
const handleSessionClosed = () => {
|
||||
sessionId = null;
|
||||
handleSessionDone();
|
||||
};
|
||||
|
||||
const handleChange = (value, skipUndoChain) =>
|
||||
// @ts-ignore
|
||||
dispatchModel({
|
||||
type: 'compute',
|
||||
useMerge: skipUndoChain,
|
||||
compute: v => (_.isFunction(value) ? value(v) : value),
|
||||
});
|
||||
|
||||
const { editorState, editorValue, setEditorData } = useEditorData({
|
||||
tabid,
|
||||
onInitialData: value => {
|
||||
dispatchModel({ type: 'reset', value });
|
||||
},
|
||||
});
|
||||
|
||||
const [modelState, dispatchModel] = createUndoReducer({
|
||||
tables: [],
|
||||
references: [],
|
||||
columns: [],
|
||||
});
|
||||
// {visibleResultTabs && (
|
||||
// <TabPage label="Messages" key="messages">
|
||||
// <SocketMessagesView
|
||||
// eventName={sessionId ? `session-info-${sessionId}` : null}
|
||||
// executeNumber={executeNumber}
|
||||
// />
|
||||
// </TabPage>
|
||||
// )}
|
||||
function createMenu() {
|
||||
return [
|
||||
{ command: 'designer.execute' },
|
||||
{ command: 'designer.kill' },
|
||||
{ command: 'designer.openSql' },
|
||||
{ divider: true },
|
||||
{ command: 'designer.save' },
|
||||
{ command: 'designer.saveAs' },
|
||||
{ divider: true },
|
||||
{ command: 'designer.undo' },
|
||||
{ command: 'designer.redo' },
|
||||
{ divider: true },
|
||||
{
|
||||
text: `Remove duplicates: ${$editorState.value?.settings?.isDistinct ? 'YES' : 'NO'}`,
|
||||
onClick: () => {
|
||||
handleChange(
|
||||
{
|
||||
...$editorState.value,
|
||||
settings: {
|
||||
...$editorState.value?.settings,
|
||||
isDistinct: !$editorState.value?.settings?.isDistinct,
|
||||
},
|
||||
},
|
||||
false
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const quickExportHandlerRef = createQuickExportHandlerRef();
|
||||
</script>
|
||||
|
||||
<ToolStripContainer>
|
||||
<VerticalSplitter initialValue="70%">
|
||||
<svelte:fragment slot="1">
|
||||
<QueryDesigner
|
||||
value={$modelState.value || {}}
|
||||
{conid}
|
||||
{database}
|
||||
engine={$connection && $connection.engine}
|
||||
onChange={handleChange}
|
||||
menu={createMenu}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="2">
|
||||
<ResultTabs
|
||||
tabs={[
|
||||
{
|
||||
label: 'Columns',
|
||||
component: QueryDesignColumns,
|
||||
props: {
|
||||
value: $modelState.value || {},
|
||||
onChange: handleChange,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'SQL',
|
||||
component: SqlEditor,
|
||||
props: {
|
||||
engine: $connection && $connection.engine,
|
||||
readOnly: true,
|
||||
value: sqlPreview,
|
||||
},
|
||||
},
|
||||
visibleResultTabs && { label: 'Messages', slot: 0 },
|
||||
]}
|
||||
{sessionId}
|
||||
{executeNumber}
|
||||
>
|
||||
<svelte:fragment slot="0">
|
||||
<SocketMessageView
|
||||
eventName={sessionId ? `session-info-${sessionId}` : null}
|
||||
{executeNumber}
|
||||
showProcedure
|
||||
showLine
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</ResultTabs>
|
||||
</svelte:fragment>
|
||||
</VerticalSplitter>
|
||||
<svelte:fragment slot="toolstrip">
|
||||
<ToolStripCommandButton command="designer.execute" />
|
||||
<ToolStripCommandButton command="designer.kill" />
|
||||
<ToolStripCommandButton command="designer.openSql" />
|
||||
<ToolStripSaveButton idPrefix="designer" />
|
||||
<ToolStripExportButton command="jslTableGrid.export" {quickExportHandlerRef} label="Export result" />
|
||||
</svelte:fragment>
|
||||
</ToolStripContainer>
|
||||
@@ -11,6 +11,7 @@ import * as MarkdownEditorTab from './MarkdownEditorTab.svelte';
|
||||
import * as MarkdownViewTab from './MarkdownViewTab.svelte';
|
||||
import * as MarkdownPreviewTab from './MarkdownPreviewTab.svelte';
|
||||
import * as FavoriteEditorTab from './FavoriteEditorTab.svelte';
|
||||
import * as QueryDesignTab from './QueryDesignTab.svelte';
|
||||
import * as CommandListTab from './CommandListTab.svelte';
|
||||
import * as YamlEditorTab from './YamlEditorTab.svelte';
|
||||
import * as JsonEditorTab from './JsonEditorTab.svelte';
|
||||
@@ -22,6 +23,7 @@ import * as DbKeyDetailTab from './DbKeyDetailTab.svelte';
|
||||
import * as QueryDataTab from './QueryDataTab.svelte';
|
||||
import * as ConnectionTab from './ConnectionTab.svelte';
|
||||
import * as MapTab from './MapTab.svelte';
|
||||
import * as PerspectiveTab from './PerspectiveTab.svelte';
|
||||
import * as ServerSummaryTab from './ServerSummaryTab.svelte';
|
||||
import * as ProfilerTab from './ProfilerTab.svelte';
|
||||
import * as DataDuplicatorTab from './DataDuplicatorTab.svelte';
|
||||
@@ -44,6 +46,7 @@ export default {
|
||||
MarkdownViewTab,
|
||||
MarkdownPreviewTab,
|
||||
FavoriteEditorTab,
|
||||
QueryDesignTab,
|
||||
CommandListTab,
|
||||
YamlEditorTab,
|
||||
JsonEditorTab,
|
||||
@@ -55,6 +58,7 @@ export default {
|
||||
QueryDataTab,
|
||||
ConnectionTab,
|
||||
MapTab,
|
||||
PerspectiveTab,
|
||||
ServerSummaryTab,
|
||||
ProfilerTab,
|
||||
DataDuplicatorTab,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
export let getSelectedObject;
|
||||
export let selectedObjectMatcher;
|
||||
export let handleObjectClick;
|
||||
export let handleExpansion = null;
|
||||
|
||||
export let onScrollTop = null;
|
||||
export let onFocusFilterBox = null;
|
||||
@@ -82,12 +81,6 @@
|
||||
handleObjectClick?.(listInstance[listInstance.length - 1], { tabPreviewMode: true });
|
||||
}
|
||||
}
|
||||
if (ev.keyCode == keycodes.numPadAdd) {
|
||||
handleExpansion?.(getSelectedObject(), true);
|
||||
}
|
||||
if (ev.keyCode == keycodes.numPadSub) {
|
||||
handleExpansion?.(getSelectedObject(), false);
|
||||
}
|
||||
|
||||
if (
|
||||
!ev.ctrlKey &&
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
function getFocusFlatList() {
|
||||
const expanded = $expandedConnections;
|
||||
const opened = $openedConnections;
|
||||
const status = $serverStatus;
|
||||
|
||||
const res = [];
|
||||
for (const con of [...connectionsWithParent, ...connectionsWithoutParent]) {
|
||||
@@ -92,7 +91,7 @@
|
||||
database: con.singleDatabase ? con.defaultDatabase : null,
|
||||
});
|
||||
|
||||
if ((expanded.includes(con._id) && opened.includes(con._id) && status?.[con._id]?.name == 'ok') || filter) {
|
||||
if ((expanded.includes(con._id) && opened.includes(con._id)) || filter) {
|
||||
for (const db of _.sortBy(databases, x => x.sortOrder ?? x.name)) {
|
||||
if (!filterName(filter, con.displayName, con.server, db.name)) {
|
||||
continue;
|
||||
@@ -165,13 +164,6 @@
|
||||
{ text: 'Delete', onClick: handleDelete },
|
||||
];
|
||||
}
|
||||
|
||||
$: addNewConnectionsButton =
|
||||
!!$connections &&
|
||||
!$connections.find(x => !x.unsaved) &&
|
||||
$openedConnections.length == 0 &&
|
||||
$commandsCustomized['new.connection']?.enabled &&
|
||||
!$openedTabs.find(x => !x.closedTime && x.tabComponent == 'ConnectionTab' && !x.props?.conid);
|
||||
</script>
|
||||
|
||||
<SearchBoxWrapper>
|
||||
@@ -205,109 +197,102 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if addNewConnectionsButton}
|
||||
<AppObjectListHandler
|
||||
bind:this={domListHandler}
|
||||
list={getFocusFlatList}
|
||||
selectedObjectStore={focusedConnectionOrDatabase}
|
||||
getSelectedObject={getFocusedConnectionOrDatabase}
|
||||
selectedObjectMatcher={(o1, o2) => o1?.conid == o2?.conid && o1?.database == o2?.database}
|
||||
getDefaultFocusedItem={() =>
|
||||
$currentDatabase
|
||||
? {
|
||||
conid: $currentDatabase?.connection?._id,
|
||||
database: $currentDatabase?.name,
|
||||
connection: $currentDatabase?.connection,
|
||||
}
|
||||
: null}
|
||||
onScrollTop={() => {
|
||||
domContainer?.scrollTop();
|
||||
}}
|
||||
onFocusFilterBox={text => {
|
||||
domFilter?.focus(text);
|
||||
}}
|
||||
handleObjectClick={(data, options) => {
|
||||
if (data.database) {
|
||||
if (options.focusTab) {
|
||||
if ($openedSingleDatabaseConnections.includes(data.conid)) {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
} else {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
}
|
||||
// console.log('FOCUSING DB', passProps);
|
||||
// passProps?.onFocusSqlObjectList?.();
|
||||
}
|
||||
} else {
|
||||
if (options.focusTab) {
|
||||
openConnection(data.connection);
|
||||
} else {
|
||||
const config = getCurrentConfig();
|
||||
if (config.runAsPortal == false && !config.storageDatabase) {
|
||||
openNewTab({
|
||||
title: getConnectionLabel(data.connection),
|
||||
icon: 'img connection',
|
||||
tabComponent: 'ConnectionTab',
|
||||
tabPreviewMode: options.tabPreviewMode,
|
||||
props: {
|
||||
conid: data.conid,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AppObjectList
|
||||
list={connectionsWithParent}
|
||||
module={connectionAppObject}
|
||||
subItemsComponent={() => SubDatabaseList}
|
||||
expandOnClick
|
||||
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
|
||||
{filter}
|
||||
passProps={{ ...passProps, connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
|
||||
}}
|
||||
groupIconFunc={chevronExpandIcon}
|
||||
groupFunc={data => data.parent}
|
||||
expandIconFunc={plusExpandIcon}
|
||||
onDropOnGroup={handleDropOnGroup}
|
||||
emptyGroupNames={$emptyConnectionGroupNames}
|
||||
sortGroups
|
||||
groupContextMenu={createGroupContextMenu}
|
||||
collapsedGroupNames={collapsedConnectionGroupNames}
|
||||
/>
|
||||
{#if (connectionsWithParent?.length > 0 && connectionsWithoutParent?.length > 0) || ($emptyConnectionGroupNames.length > 0 && connectionsWithoutParent?.length > 0)}
|
||||
<div class="br" />
|
||||
{/if}
|
||||
<AppObjectList
|
||||
list={connectionsWithoutParent}
|
||||
module={connectionAppObject}
|
||||
subItemsComponent={() => SubDatabaseList}
|
||||
expandOnClick
|
||||
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
|
||||
{filter}
|
||||
passProps={{ connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
|
||||
}}
|
||||
/>
|
||||
</AppObjectListHandler>
|
||||
{#if $connections && !$connections.find(x => !x.unsaved) && $openedConnections.length == 0 && $commandsCustomized['new.connection']?.enabled && !$openedTabs.find(x => !x.closedTime && x.tabComponent == 'ConnectionTab' && !x.props?.conid)}
|
||||
<LargeButton icon="icon new-connection" on:click={() => runCommand('new.connection')} fillHorizontal
|
||||
>Add new connection</LargeButton
|
||||
>
|
||||
<!-- <ToolbarButton icon="icon new-connection" on:click={() => runCommand('new.connection')}>
|
||||
Add new connection
|
||||
</ToolbarButton> -->
|
||||
{:else}
|
||||
<AppObjectListHandler
|
||||
bind:this={domListHandler}
|
||||
list={getFocusFlatList}
|
||||
selectedObjectStore={focusedConnectionOrDatabase}
|
||||
getSelectedObject={getFocusedConnectionOrDatabase}
|
||||
selectedObjectMatcher={(o1, o2) => o1?.conid == o2?.conid && o1?.database == o2?.database}
|
||||
getDefaultFocusedItem={() =>
|
||||
$currentDatabase
|
||||
? {
|
||||
conid: $currentDatabase?.connection?._id,
|
||||
database: $currentDatabase?.name,
|
||||
connection: $currentDatabase?.connection,
|
||||
}
|
||||
: null}
|
||||
onScrollTop={() => {
|
||||
domContainer?.scrollTop();
|
||||
}}
|
||||
onFocusFilterBox={text => {
|
||||
domFilter?.focus(text);
|
||||
}}
|
||||
handleObjectClick={(data, options) => {
|
||||
if (data.database) {
|
||||
if (options.focusTab) {
|
||||
if ($openedSingleDatabaseConnections.includes(data.conid)) {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
} else {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
}
|
||||
// console.log('FOCUSING DB', passProps);
|
||||
// passProps?.onFocusSqlObjectList?.();
|
||||
}
|
||||
} else {
|
||||
if (options.focusTab) {
|
||||
openConnection(data.connection);
|
||||
} else {
|
||||
const config = getCurrentConfig();
|
||||
if (config.runAsPortal == false && !config.storageDatabase) {
|
||||
openNewTab({
|
||||
title: getConnectionLabel(data.connection),
|
||||
icon: 'img connection',
|
||||
tabComponent: 'ConnectionTab',
|
||||
tabPreviewMode: options.tabPreviewMode,
|
||||
props: {
|
||||
conid: data.conid,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
handleExpansion={(item, value) => {
|
||||
if (item.database) {
|
||||
return;
|
||||
}
|
||||
expandedConnections.update(old => (value ? [...old, item.conid] : old.filter(x => x != item.conid)));
|
||||
}}
|
||||
>
|
||||
<AppObjectList
|
||||
list={connectionsWithParent}
|
||||
module={connectionAppObject}
|
||||
subItemsComponent={SubDatabaseList}
|
||||
expandOnClick
|
||||
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
|
||||
{filter}
|
||||
passProps={{ ...passProps, connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
|
||||
}}
|
||||
groupIconFunc={chevronExpandIcon}
|
||||
groupFunc={data => data.parent}
|
||||
expandIconFunc={plusExpandIcon}
|
||||
onDropOnGroup={handleDropOnGroup}
|
||||
emptyGroupNames={$emptyConnectionGroupNames}
|
||||
sortGroups
|
||||
groupContextMenu={createGroupContextMenu}
|
||||
collapsedGroupNames={collapsedConnectionGroupNames}
|
||||
/>
|
||||
{#if (connectionsWithParent?.length > 0 && connectionsWithoutParent?.length > 0) || ($emptyConnectionGroupNames.length > 0 && connectionsWithoutParent?.length > 0)}
|
||||
<div class="br" />
|
||||
{/if}
|
||||
<AppObjectList
|
||||
list={connectionsWithoutParent}
|
||||
module={connectionAppObject}
|
||||
subItemsComponent={SubDatabaseList}
|
||||
expandOnClick
|
||||
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
|
||||
{filter}
|
||||
passProps={{ connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
|
||||
}}
|
||||
/>
|
||||
</AppObjectListHandler>
|
||||
Add new connection
|
||||
</ToolbarButton> -->
|
||||
{/if}
|
||||
</WidgetsInnerContainer>
|
||||
|
||||
|
||||
@@ -5,17 +5,10 @@
|
||||
import { focusedConnectionOrDatabase, openedConnections } from '../stores';
|
||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||
import { openConnection } from '../appobj/ConnectionAppObject.svelte';
|
||||
import { useServerStatus } from '../utility/metadataLoaders';
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
export let connection;
|
||||
|
||||
$: serverStatus = useServerStatus();
|
||||
$: focusedServerStatus = $focusedConnectionOrDatabase?.conid
|
||||
? $serverStatus?.[$focusedConnectionOrDatabase?.conid]
|
||||
: null;
|
||||
</script>
|
||||
|
||||
<div class="no-focused-info">
|
||||
@@ -39,7 +32,7 @@
|
||||
<FormStyledButton
|
||||
value={`Show ${database}`}
|
||||
skipWidth
|
||||
outline
|
||||
outline
|
||||
on:click={() => {
|
||||
$focusedConnectionOrDatabase = {
|
||||
conid,
|
||||
@@ -50,11 +43,6 @@
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
{#if focusedServerStatus?.name == 'error' && focusedServerStatus?.message}
|
||||
<div class="m-1">Error connecting <b>{getConnectionLabel($focusedConnectionOrDatabase?.connection)}</b>:</div>
|
||||
<ErrorInfo message={focusedServerStatus?.message} />
|
||||
<div class="m-3" />
|
||||
{/if}
|
||||
{#if connection}
|
||||
<div class="m-1">Current connection:</div>
|
||||
<div class="m-1 ml-3 mb-3">
|
||||
|
||||
@@ -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;
|
||||
@@ -60,7 +61,6 @@
|
||||
let domContainer = null;
|
||||
let domFilter = null;
|
||||
let domListHandler;
|
||||
let expandedObjects = [];
|
||||
|
||||
$: objects = useDatabaseInfo({ conid, database });
|
||||
$: status = useDatabaseStatus({ conid, database });
|
||||
@@ -226,11 +226,6 @@
|
||||
onFocusFilterBox={text => {
|
||||
domFilter?.focus(text);
|
||||
}}
|
||||
handleExpansion={(data, value) => {
|
||||
expandedObjects = value
|
||||
? [...expandedObjects, `${data.objectTypeField}||${data.schemaName}||${data.pureName}`]
|
||||
: expandedObjects.filter(x => x != `${data.objectTypeField}||${data.schemaName}||${data.pureName}`);
|
||||
}}
|
||||
>
|
||||
<AppObjectList
|
||||
list={objectList
|
||||
@@ -238,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={{
|
||||
@@ -248,13 +250,6 @@
|
||||
connection: $connection,
|
||||
hideSchemaName: !!$appliedCurrentSchema,
|
||||
}}
|
||||
getIsExpanded={data =>
|
||||
expandedObjects.includes(`${data.objectTypeField}||${data.schemaName}||${data.pureName}`)}
|
||||
setIsExpanded={(data, value) => {
|
||||
expandedObjects = value
|
||||
? [...expandedObjects, `${data.objectTypeField}||${data.schemaName}||${data.pureName}`]
|
||||
: expandedObjects.filter(x => x != `${data.objectTypeField}||${data.schemaName}||${data.pureName}`);
|
||||
}}
|
||||
/>
|
||||
</AppObjectListHandler>
|
||||
{/if}
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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;
|
||||
`;
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
`;
|
||||
@@ -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 });
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
`;
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`],
|
||||
})),
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
`;
|
||||
Reference in New Issue
Block a user