mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-29 04:06:00 +00:00
Merge branch 'feature/db-schema'
This commit is contained in:
@@ -67,3 +67,4 @@ describe('Alter database', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
86
integration-tests/__tests__/schema-tests.spec.js
Normal file
86
integration-tests/__tests__/schema-tests.spec.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
const stableStringify = require('json-stable-stringify');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const fp = require('lodash/fp');
|
||||||
|
const { testWrapper } = require('../tools');
|
||||||
|
const engines = require('../engines');
|
||||||
|
const { runCommandOnDriver } = require('dbgate-tools');
|
||||||
|
|
||||||
|
async function baseStructure(conn, driver) {
|
||||||
|
await driver.query(conn, `create table t1 (id int not null primary key)`);
|
||||||
|
|
||||||
|
await driver.query(
|
||||||
|
conn,
|
||||||
|
`create table t2 (
|
||||||
|
id int not null primary key,
|
||||||
|
t1_id int
|
||||||
|
)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Schema tests', () => {
|
||||||
|
test.each(engines.filter(x => x.supportSchemas).map(engine => [engine.label, engine]))(
|
||||||
|
'Create schema - %s',
|
||||||
|
testWrapper(async (conn, driver, engine) => {
|
||||||
|
await baseStructure(conn, driver);
|
||||||
|
const structure1 = await driver.analyseFull(conn);
|
||||||
|
expect(structure1.schemas.find(x => x.schemaName == 'myschema')).toBeFalsy();
|
||||||
|
const count = structure1.schemas.length;
|
||||||
|
expect(structure1.tables.length).toEqual(2);
|
||||||
|
await runCommandOnDriver(conn, driver, dmp => dmp.createSchema('myschema'));
|
||||||
|
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||||
|
expect(structure2.schemas.find(x => x.schemaName == 'myschema')).toBeTruthy();
|
||||||
|
expect(structure2.tables.length).toEqual(2);
|
||||||
|
expect(structure2.schemas.length).toEqual(count + 1);
|
||||||
|
|
||||||
|
const structure3 = await driver.analyseIncremental(conn, structure2);
|
||||||
|
expect(structure3).toBeNull();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each(engines.filter(x => x.supportSchemas).map(engine => [engine.label, engine]))(
|
||||||
|
'Drop schema - %s',
|
||||||
|
testWrapper(async (conn, driver, engine) => {
|
||||||
|
await baseStructure(conn, driver);
|
||||||
|
await runCommandOnDriver(conn, driver, dmp => dmp.createSchema('myschema'));
|
||||||
|
|
||||||
|
const structure1 = await driver.analyseFull(conn);
|
||||||
|
expect(structure1.schemas.find(x => x.schemaName == 'myschema')).toBeTruthy();
|
||||||
|
expect(structure1.tables.length).toEqual(2);
|
||||||
|
await runCommandOnDriver(conn, driver, dmp => dmp.dropSchema('myschema'));
|
||||||
|
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||||
|
expect(structure2.schemas.find(x => x.schemaName == 'myschema')).toBeFalsy();
|
||||||
|
expect(structure2.tables.length).toEqual(2);
|
||||||
|
|
||||||
|
const structure3 = await driver.analyseIncremental(conn, structure2);
|
||||||
|
expect(structure3).toBeNull();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each(engines.filter(x => x.supportSchemas).map(engine => [engine.label, engine]))(
|
||||||
|
'Create table - keep schemas - %s',
|
||||||
|
testWrapper(async (conn, driver, engine) => {
|
||||||
|
await baseStructure(conn, driver);
|
||||||
|
const structure1 = await driver.analyseFull(conn);
|
||||||
|
const count = structure1.schemas.length;
|
||||||
|
expect(structure1.tables.length).toEqual(2);
|
||||||
|
await driver.query(conn, `create table t3 (id int not null primary key)`);
|
||||||
|
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||||
|
expect(structure2.tables.length).toEqual(3);
|
||||||
|
expect(structure2.schemas.length).toEqual(count);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Base analyser test', () => {
|
||||||
|
test.each(engines.map(engine => [engine.label, engine]))(
|
||||||
|
'Structure without change - %s',
|
||||||
|
testWrapper(async (conn, driver, engine) => {
|
||||||
|
await baseStructure(conn, driver);
|
||||||
|
|
||||||
|
const structure1 = await driver.analyseFull(conn);
|
||||||
|
expect(structure1.tables.length).toEqual(2);
|
||||||
|
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||||
|
expect(structure2).toBeNull();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
# postgres:
|
postgres:
|
||||||
# image: postgres
|
image: postgres
|
||||||
# restart: always
|
restart: always
|
||||||
# environment:
|
environment:
|
||||||
# POSTGRES_PASSWORD: Pwd2020Db
|
POSTGRES_PASSWORD: Pwd2020Db
|
||||||
# ports:
|
ports:
|
||||||
# - 15000:5432
|
- 15000:5432
|
||||||
|
|
||||||
# mariadb:
|
# mariadb:
|
||||||
# image: mariadb
|
# image: mariadb
|
||||||
@@ -26,13 +26,13 @@ services:
|
|||||||
# environment:
|
# environment:
|
||||||
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
|
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
|
||||||
|
|
||||||
clickhouse:
|
# clickhouse:
|
||||||
image: bitnami/clickhouse:24.8.4
|
# image: bitnami/clickhouse:24.8.4
|
||||||
restart: always
|
# restart: always
|
||||||
ports:
|
# ports:
|
||||||
- 15005:8123
|
# - 15005:8123
|
||||||
environment:
|
# environment:
|
||||||
- CLICKHOUSE_ADMIN_PASSWORD=Pwd2020Db
|
# - CLICKHOUSE_ADMIN_PASSWORD=Pwd2020Db
|
||||||
|
|
||||||
# mssql:
|
# mssql:
|
||||||
# image: mcr.microsoft.com/mssql/server
|
# image: mcr.microsoft.com/mssql/server
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ const engines = [
|
|||||||
drop2: 'DROP FUNCTION obj2',
|
drop2: 'DROP FUNCTION obj2',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
supportSchemas: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'SQL Server',
|
label: 'SQL Server',
|
||||||
@@ -105,6 +106,7 @@ const engines = [
|
|||||||
drop2: 'DROP PROCEDURE obj2',
|
drop2: 'DROP PROCEDURE obj2',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
supportSchemas: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'SQLite',
|
label: 'SQLite',
|
||||||
@@ -159,11 +161,11 @@ const filterLocal = [
|
|||||||
// filter local testing
|
// filter local testing
|
||||||
'-MySQL',
|
'-MySQL',
|
||||||
'-MariaDB',
|
'-MariaDB',
|
||||||
'-PostgreSQL',
|
'PostgreSQL',
|
||||||
'-SQL Server',
|
'-SQL Server',
|
||||||
'-SQLite',
|
'-SQLite',
|
||||||
'-CockroachDB',
|
'-CockroachDB',
|
||||||
'ClickHouse',
|
'-ClickHouse',
|
||||||
];
|
];
|
||||||
|
|
||||||
const enginesPostgre = engines.filter(x => x.label == 'PostgreSQL');
|
const enginesPostgre = engines.filter(x => x.label == 'PostgreSQL');
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import _pick from 'lodash/pick';
|
|||||||
import _compact from 'lodash/compact';
|
import _compact from 'lodash/compact';
|
||||||
import { getLogger } from './getLogger';
|
import { getLogger } from './getLogger';
|
||||||
import { type Logger } from 'pinomin';
|
import { type Logger } from 'pinomin';
|
||||||
|
import stableStringify from 'json-stable-stringify';
|
||||||
|
|
||||||
const logger = getLogger('dbAnalyser');
|
const logger = getLogger('dbAnalyser');
|
||||||
|
|
||||||
@@ -70,7 +71,10 @@ export class DatabaseAnalyser {
|
|||||||
async fullAnalysis() {
|
async fullAnalysis() {
|
||||||
const res = this.addEngineField(await this._runAnalysis());
|
const res = this.addEngineField(await this._runAnalysis());
|
||||||
// console.log('FULL ANALYSIS', res);
|
// console.log('FULL ANALYSIS', res);
|
||||||
return res;
|
return {
|
||||||
|
...res,
|
||||||
|
schemas: await this.readSchemaList(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async singleObjectAnalysis(name, typeField) {
|
async singleObjectAnalysis(name, typeField) {
|
||||||
@@ -87,6 +91,10 @@ export class DatabaseAnalyser {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async readSchemaList() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
async incrementalAnalysis(structure) {
|
async incrementalAnalysis(structure) {
|
||||||
this.structure = structure;
|
this.structure = structure;
|
||||||
|
|
||||||
@@ -99,22 +107,35 @@ export class DatabaseAnalyser {
|
|||||||
const structureModifications = modifications.filter(x => x.action != 'setTableRowCounts');
|
const structureModifications = modifications.filter(x => x.action != 'setTableRowCounts');
|
||||||
const setTableRowCounts = modifications.find(x => x.action == 'setTableRowCounts');
|
const setTableRowCounts = modifications.find(x => x.action == 'setTableRowCounts');
|
||||||
|
|
||||||
let structureWithRowCounts = null;
|
let structureUpdated = null;
|
||||||
if (setTableRowCounts) {
|
if (setTableRowCounts) {
|
||||||
const newStructure = mergeTableRowCounts(structure, setTableRowCounts.rowCounts);
|
const newStructure = mergeTableRowCounts(structure, setTableRowCounts.rowCounts);
|
||||||
if (areDifferentRowCounts(structure, newStructure)) {
|
if (areDifferentRowCounts(structure, newStructure)) {
|
||||||
structureWithRowCounts = newStructure;
|
structureUpdated = newStructure;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const schemas = await this.readSchemaList();
|
||||||
|
const areSchemasDifferent = stableStringify(schemas) != stableStringify(this.structure.schemas);
|
||||||
|
if (areSchemasDifferent) {
|
||||||
|
structureUpdated = {
|
||||||
|
...structure,
|
||||||
|
...structureUpdated,
|
||||||
|
schemas,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (structureModifications.length == 0) {
|
if (structureModifications.length == 0) {
|
||||||
return structureWithRowCounts ? this.addEngineField(structureWithRowCounts) : null;
|
return structureUpdated ? this.addEngineField(structureUpdated) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modifications = structureModifications;
|
this.modifications = structureModifications;
|
||||||
if (structureWithRowCounts) this.structure = structureWithRowCounts;
|
if (structureUpdated) this.structure = structureUpdated;
|
||||||
logger.info({ modifications: this.modifications }, 'DB modifications detected:');
|
logger.info({ modifications: this.modifications }, 'DB modifications detected:');
|
||||||
return this.addEngineField(this.mergeAnalyseResult(await this._runAnalysis()));
|
return {
|
||||||
|
...this.addEngineField(this.mergeAnalyseResult(await this._runAnalysis())),
|
||||||
|
schemas,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeAnalyseResult(newlyAnalysed) {
|
mergeAnalyseResult(newlyAnalysed) {
|
||||||
|
|||||||
@@ -214,6 +214,14 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
this.putCmd('^drop ^database %i', name);
|
this.putCmd('^drop ^database %i', name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createSchema(name: string) {
|
||||||
|
this.putCmd('^create ^schema %i', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
dropSchema(name: string) {
|
||||||
|
this.putCmd('^drop ^schema %i', name);
|
||||||
|
}
|
||||||
|
|
||||||
specialColumnOptions(column) {}
|
specialColumnOptions(column) {}
|
||||||
|
|
||||||
selectScopeIdentity(table: TableInfo) {}
|
selectScopeIdentity(table: TableInfo) {}
|
||||||
|
|||||||
@@ -84,7 +84,17 @@ export const driverBase = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async operation(pool, operation, options: RunScriptOptions) {
|
async operation(pool, operation, options: RunScriptOptions) {
|
||||||
throw new Error('Operation not defined in target driver');
|
const { type } = operation;
|
||||||
|
switch (type) {
|
||||||
|
case 'createSchema':
|
||||||
|
await runCommandOnDriver(pool, this, dmp => dmp.createSchema(operation.schemaName));
|
||||||
|
break;
|
||||||
|
case 'dropSchema':
|
||||||
|
await runCommandOnDriver(pool, this, dmp => dmp.dropSchema(operation.schemaName));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Operation type ${type} not supported`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getNewObjectTemplates() {
|
getNewObjectTemplates() {
|
||||||
if (this.databaseEngineTypes.includes('sql')) {
|
if (this.databaseEngineTypes.includes('sql')) {
|
||||||
@@ -180,5 +190,5 @@ export const driverBase = {
|
|||||||
|
|
||||||
adaptTableInfo(table) {
|
adaptTableInfo(table) {
|
||||||
return table;
|
return table;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,27 +56,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleNewTable = () => {
|
const handleNewTable = () => {
|
||||||
const tooltip = `${getConnectionLabel(connection)}\n${name}`;
|
newTable(connection, name);
|
||||||
openNewTab(
|
|
||||||
{
|
|
||||||
title: 'Table #',
|
|
||||||
tooltip,
|
|
||||||
icon: 'img table-structure',
|
|
||||||
tabComponent: 'TableStructureTab',
|
|
||||||
props: {
|
|
||||||
conid: connection._id,
|
|
||||||
database: name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
editor: {
|
|
||||||
columns: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
forceNewTab: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDropDatabase = () => {
|
const handleDropDatabase = () => {
|
||||||
@@ -410,6 +390,7 @@
|
|||||||
import NewCollectionModal from '../modals/NewCollectionModal.svelte';
|
import NewCollectionModal from '../modals/NewCollectionModal.svelte';
|
||||||
import hasPermission from '../utility/hasPermission';
|
import hasPermission from '../utility/hasPermission';
|
||||||
import { openImportExportTab } from '../utility/importExportTools';
|
import { openImportExportTab } from '../utility/importExportTools';
|
||||||
|
import newTable from '../tableeditor/newTable';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
export let passProps;
|
export let passProps;
|
||||||
|
|||||||
@@ -877,7 +877,7 @@
|
|||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
module={$$props.module}
|
module={$$props.module}
|
||||||
{data}
|
{data}
|
||||||
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
|
title={data.schemaName && !passProps?.hideSchemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
|
||||||
icon={databaseObjectIcons[data.objectTypeField]}
|
icon={databaseObjectIcons[data.objectTypeField]}
|
||||||
menu={createMenu}
|
menu={createMenu}
|
||||||
showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin}
|
showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import NewCollectionModal from '../modals/NewCollectionModal.svelte';
|
|||||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||||
import localforage from 'localforage';
|
import localforage from 'localforage';
|
||||||
import { openImportExportTab } from '../utility/importExportTools';
|
import { openImportExportTab } from '../utility/importExportTools';
|
||||||
|
import newTable from '../tableeditor/newTable';
|
||||||
|
|
||||||
// function themeCommand(theme: ThemeDefinition) {
|
// function themeCommand(theme: ThemeDefinition) {
|
||||||
// return {
|
// return {
|
||||||
@@ -253,26 +254,7 @@ registerCommand({
|
|||||||
const $currentDatabase = get(currentDatabase);
|
const $currentDatabase = get(currentDatabase);
|
||||||
const connection = _.get($currentDatabase, 'connection') || {};
|
const connection = _.get($currentDatabase, 'connection') || {};
|
||||||
const database = _.get($currentDatabase, 'name');
|
const database = _.get($currentDatabase, 'name');
|
||||||
|
newTable(connection, database);
|
||||||
openNewTab(
|
|
||||||
{
|
|
||||||
title: 'Table #',
|
|
||||||
icon: 'img table-structure',
|
|
||||||
tabComponent: 'TableStructureTab',
|
|
||||||
props: {
|
|
||||||
conid: connection._id,
|
|
||||||
database,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
editor: {
|
|
||||||
columns: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
forceNewTab: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
import FormArgumentList from '../forms/FormArgumentList.svelte';
|
import FormArgumentList from '../forms/FormArgumentList.svelte';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||||
import createRef from '../utility/createRef';
|
import FormTextField from '../forms/FormTextField.svelte';
|
||||||
|
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||||
|
|
||||||
export let title;
|
export let title;
|
||||||
export let fieldDefinitions;
|
export let fieldDefinitions;
|
||||||
export let values;
|
export let values;
|
||||||
export let onChangeValues;
|
export let onChangeValues;
|
||||||
|
export let pureNameTitle = null;
|
||||||
|
export let schemaList = null;
|
||||||
|
|
||||||
let collapsed = false;
|
let collapsed = false;
|
||||||
|
|
||||||
@@ -32,6 +35,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if !collapsed}
|
{#if !collapsed}
|
||||||
<FormProviderCore values={valuesStore}>
|
<FormProviderCore values={valuesStore}>
|
||||||
|
{#if schemaList?.length > 0}
|
||||||
|
<FormSelectField
|
||||||
|
isNative
|
||||||
|
name="schemaName"
|
||||||
|
label="Schema"
|
||||||
|
options={schemaList.map(x => ({ label: x.schemaName, value: x.schemaName }))}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if pureNameTitle}
|
||||||
|
<FormTextField name="pureName" label={pureNameTitle} />
|
||||||
|
{/if}
|
||||||
<FormArgumentList args={fieldDefinitions} />
|
<FormArgumentList args={fieldDefinitions} />
|
||||||
</FormProviderCore>
|
</FormProviderCore>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
export let isMulti = false;
|
export let isMulti = false;
|
||||||
export let notSelected = null;
|
export let notSelected = null;
|
||||||
export let defaultValue = '';
|
export let defaultValue = '';
|
||||||
|
export let selectClass = '';
|
||||||
|
|
||||||
let listOpen = false;
|
let listOpen = false;
|
||||||
let isFocused = false;
|
let isFocused = false;
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
{#if isNative}
|
{#if isNative}
|
||||||
<select
|
<select
|
||||||
value={options.find(x => x.value == value) ? value : defaultValue}
|
value={options.find(x => x.value == value) ? value : defaultValue}
|
||||||
|
class={selectClass}
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
dispatch('change', e.target['value']);
|
dispatch('change', e.target['value']);
|
||||||
@@ -46,7 +48,7 @@
|
|||||||
items={options}
|
items={options}
|
||||||
value={isMulti
|
value={isMulti
|
||||||
? _.compact(value?.map(item => options.find(x => x.value == item)) ?? [])
|
? _.compact(value?.map(item => options.find(x => x.value == item)) ?? [])
|
||||||
: options.find(x => x.value == value) ?? null}
|
: (options.find(x => x.value == value) ?? null)}
|
||||||
on:select={e => {
|
on:select={e => {
|
||||||
if (isMulti) {
|
if (isMulti) {
|
||||||
dispatch(
|
dispatch(
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
'icon minus-box': 'mdi mdi-minus-box-outline',
|
'icon minus-box': 'mdi mdi-minus-box-outline',
|
||||||
'icon plus-box': 'mdi mdi-plus-box-outline',
|
'icon plus-box': 'mdi mdi-plus-box-outline',
|
||||||
'icon plus-thick': 'mdi mdi-plus-thick',
|
'icon plus-thick': 'mdi mdi-plus-thick',
|
||||||
|
'icon minus-thick': 'mdi mdi-minus-thick',
|
||||||
'icon invisible-box': 'mdi mdi-minus-box-outline icon-invisible',
|
'icon invisible-box': 'mdi mdi-minus-box-outline icon-invisible',
|
||||||
'icon cloud-upload': 'mdi mdi-cloud-upload',
|
'icon cloud-upload': 'mdi mdi-cloud-upload',
|
||||||
'icon import': 'mdi mdi-application-import',
|
'icon import': 'mdi mdi-application-import',
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ export const loadingPluginStore = writable({
|
|||||||
loadingPackageName: null,
|
loadingPackageName: null,
|
||||||
});
|
});
|
||||||
export const activeDbKeysStore = writableWithStorage({}, 'activeDbKeysStore');
|
export const activeDbKeysStore = writableWithStorage({}, 'activeDbKeysStore');
|
||||||
|
export const appliedCurrentSchema = writable<string>(null);
|
||||||
|
|
||||||
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
||||||
$extensions.themes.find(x => x.themeClassName == $currentTheme)
|
$extensions.themes.find(x => x.themeClassName == $currentTheme)
|
||||||
@@ -311,3 +312,9 @@ appUpdaterActive.subscribe(value => {
|
|||||||
appUpdaterActiveValue = value;
|
appUpdaterActiveValue = value;
|
||||||
});
|
});
|
||||||
export const getAppUpdaterActive = () => appUpdaterActiveValue;
|
export const getAppUpdaterActive = () => appUpdaterActiveValue;
|
||||||
|
|
||||||
|
let appliedCurrentSchemaValue = null;
|
||||||
|
appliedCurrentSchema.subscribe(value => {
|
||||||
|
appliedCurrentSchemaValue = value;
|
||||||
|
});
|
||||||
|
export const getAppliedCurrentSchema = () => appliedCurrentSchemaValue;
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
export let dbInfo;
|
export let dbInfo;
|
||||||
export let driver;
|
export let driver;
|
||||||
export let resetCounter;
|
export let resetCounter;
|
||||||
|
export let isCreateTable;
|
||||||
|
|
||||||
$: isWritable = !!setTableInfo;
|
$: isWritable = !!setTableInfo;
|
||||||
|
|
||||||
@@ -165,15 +166,14 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{#if tableFormOptions}
|
{#if tableInfo && (tableFormOptions || isCreateTable)}
|
||||||
{#key resetCounter}
|
{#key resetCounter}
|
||||||
<ObjectFieldsEditor
|
<ObjectFieldsEditor
|
||||||
title="Table properties"
|
title="Table properties"
|
||||||
fieldDefinitions={tableFormOptions}
|
fieldDefinitions={tableFormOptions ?? []}
|
||||||
values={_.pick(
|
pureNameTitle={isCreateTable ? 'Table name' : null}
|
||||||
tableInfo,
|
schemaList={isCreateTable && dbInfo?.schemas?.length >= 0 ? dbInfo?.schemas : null}
|
||||||
tableFormOptions.map(x => x.name)
|
values={_.pick(tableInfo, ['schemaName', 'pureName', ...(tableFormOptions ?? []).map(x => x.name)])}
|
||||||
)}
|
|
||||||
onChangeValues={vals => {
|
onChangeValues={vals => {
|
||||||
if (!_.isEmpty(vals)) {
|
if (!_.isEmpty(vals)) {
|
||||||
setTableInfo(tbl => ({ ...tbl, ...vals }));
|
setTableInfo(tbl => ({ ...tbl, ...vals }));
|
||||||
|
|||||||
48
packages/web/src/tableeditor/newTable.ts
Normal file
48
packages/web/src/tableeditor/newTable.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import openNewTab from '../utility/openNewTab';
|
||||||
|
import { findEngineDriver, getConnectionLabel } from 'dbgate-tools';
|
||||||
|
import { getAppliedCurrentSchema, getExtensions } from '../stores';
|
||||||
|
|
||||||
|
export default function newTable(connection, database) {
|
||||||
|
const tooltip = `${getConnectionLabel(connection)}\n${database}`;
|
||||||
|
const driver = findEngineDriver(connection, getExtensions());
|
||||||
|
openNewTab(
|
||||||
|
{
|
||||||
|
title: 'Table #',
|
||||||
|
tooltip,
|
||||||
|
icon: 'img table-structure',
|
||||||
|
tabComponent: 'TableStructureTab',
|
||||||
|
props: {
|
||||||
|
conid: connection._id,
|
||||||
|
database,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
editor: {
|
||||||
|
current: {
|
||||||
|
pureName: 'new_table',
|
||||||
|
schemaName: getAppliedCurrentSchema() ?? driver?.dialect?.defaultSchemaName,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
columnName: 'id',
|
||||||
|
dataType: 'int',
|
||||||
|
isNullable: false,
|
||||||
|
isPrimaryKey: true,
|
||||||
|
isAutoIncrement: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primaryKey: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
columnName: 'id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
forceNewTab: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
toolbar: true,
|
toolbar: true,
|
||||||
isRelatedToTab: true,
|
isRelatedToTab: true,
|
||||||
icon: 'icon close',
|
icon: 'icon close',
|
||||||
testEnabled: () => getCurrentEditor()?.canSave(),
|
testEnabled: () => getCurrentEditor()?.canResetChanges(),
|
||||||
onClick: () => getCurrentEditor().reset(),
|
onClick: () => getCurrentEditor().reset(),
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -39,10 +39,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import registerCommand from '../commands/registerCommand';
|
import registerCommand from '../commands/registerCommand';
|
||||||
|
|
||||||
import ColumnLabel from '../elements/ColumnLabel.svelte';
|
|
||||||
import ConstraintLabel from '../elements/ConstraintLabel.svelte';
|
|
||||||
import ForeignKeyObjectListControl from '../elements/ForeignKeyObjectListControl.svelte';
|
|
||||||
|
|
||||||
import { extensions } from '../stores';
|
import { extensions } from '../stores';
|
||||||
import useEditorData from '../query/useEditorData';
|
import useEditorData from '../query/useEditorData';
|
||||||
import TableEditor from '../tableeditor/TableEditor.svelte';
|
import TableEditor from '../tableeditor/TableEditor.svelte';
|
||||||
@@ -53,15 +49,13 @@
|
|||||||
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
|
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
|
||||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||||
import { showSnackbarSuccess } from '../utility/snackbar';
|
import { showSnackbarSuccess } from '../utility/snackbar';
|
||||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
|
||||||
import { changeTab } from '../utility/common';
|
|
||||||
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
|
|
||||||
import openNewTab from '../utility/openNewTab';
|
import openNewTab from '../utility/openNewTab';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
||||||
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
||||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||||
import hasPermission from '../utility/hasPermission';
|
import hasPermission from '../utility/hasPermission';
|
||||||
|
import { changeTab } from '../utility/common';
|
||||||
|
|
||||||
export let tabid;
|
export let tabid;
|
||||||
export let conid;
|
export let conid;
|
||||||
@@ -90,30 +84,11 @@
|
|||||||
return objectTypeField == 'tables' && !!$editorValue && !$connection?.isReadOnly;
|
return objectTypeField == 'tables' && !!$editorValue && !$connection?.isReadOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function save() {
|
export function canResetChanges() {
|
||||||
if ($editorValue.base) {
|
return canSave() && !!$editorValue.base;
|
||||||
doSave(null);
|
|
||||||
} else {
|
|
||||||
showModal(InputTextModal, {
|
|
||||||
header: 'Set table name',
|
|
||||||
value: savedName || 'newTable',
|
|
||||||
label: 'Table name',
|
|
||||||
onConfirm: name => {
|
|
||||||
savedName = name;
|
|
||||||
setEditorData(tbl => ({
|
|
||||||
base: tbl.base,
|
|
||||||
current: {
|
|
||||||
...tbl.current,
|
|
||||||
pureName: name,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
doSave(name);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function doSave(createTableName) {
|
export function save() {
|
||||||
const { sql, recreates } = getAlterTableScript(
|
const { sql, recreates } = getAlterTableScript(
|
||||||
$editorValue.base,
|
$editorValue.base,
|
||||||
extendTableInfo(fillConstraintNames($editorValue.current, driver.dialect)),
|
extendTableInfo(fillConstraintNames($editorValue.current, driver.dialect)),
|
||||||
@@ -127,32 +102,30 @@
|
|||||||
sql,
|
sql,
|
||||||
recreates,
|
recreates,
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
handleConfirmSql(sql, createTableName);
|
handleConfirmSql(sql);
|
||||||
},
|
},
|
||||||
engine: driver.engine,
|
engine: driver.engine,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleConfirmSql(sql, createTableName) {
|
async function handleConfirmSql(sql) {
|
||||||
const resp = await apiCall('database-connections/run-script', { conid, database, sql, useTransaction: true });
|
const resp = await apiCall('database-connections/run-script', { conid, database, sql, useTransaction: true });
|
||||||
const { errorMessage } = resp || {};
|
const { errorMessage } = resp || {};
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
showModal(ErrorMessageModal, { title: 'Error when saving', message: errorMessage });
|
showModal(ErrorMessageModal, { title: 'Error when saving', message: errorMessage });
|
||||||
} else {
|
} else {
|
||||||
if (createTableName) {
|
|
||||||
changeTab(tabid, tab => ({
|
|
||||||
...tab,
|
|
||||||
title: createTableName,
|
|
||||||
props: {
|
|
||||||
...tab.props,
|
|
||||||
pureName: createTableName,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
await apiCall('database-connections/sync-model', { conid, database });
|
await apiCall('database-connections/sync-model', { conid, database });
|
||||||
showSnackbarSuccess('Saved to database');
|
showSnackbarSuccess('Saved to database');
|
||||||
|
const isCreateTable = $editorValue?.base == null;
|
||||||
|
const tableName = _.pick($editorValue.current, ['pureName', 'schemaName']);
|
||||||
clearEditorData();
|
clearEditorData();
|
||||||
|
if (isCreateTable) {
|
||||||
|
changeTab(tabid, tab => ({
|
||||||
|
...tab,
|
||||||
|
title: tableName.pureName,
|
||||||
|
props: { ...tab.props, ...tableName },
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +148,7 @@
|
|||||||
dbInfo={$dbInfo}
|
dbInfo={$dbInfo}
|
||||||
{driver}
|
{driver}
|
||||||
{resetCounter}
|
{resetCounter}
|
||||||
|
isCreateTable={objectTypeField == 'tables' && !$editorValue?.base}
|
||||||
setTableInfo={objectTypeField == 'tables' && !$connection?.isReadOnly && hasPermission(`dbops/model/edit`)
|
setTableInfo={objectTypeField == 'tables' && !$connection?.isReadOnly && hasPermission(`dbops/model/edit`)
|
||||||
? tableInfoUpdater =>
|
? tableInfoUpdater =>
|
||||||
setEditorData(tbl =>
|
setEditorData(tbl =>
|
||||||
@@ -191,7 +165,10 @@
|
|||||||
: null}
|
: null}
|
||||||
/>
|
/>
|
||||||
<svelte:fragment slot="toolstrip">
|
<svelte:fragment slot="toolstrip">
|
||||||
<ToolStripCommandButton command="tableStructure.save" />
|
<ToolStripCommandButton
|
||||||
|
command="tableStructure.save"
|
||||||
|
buttonLabel={$editorValue?.base ? 'Alter table' : 'Create table'}
|
||||||
|
/>
|
||||||
<ToolStripCommandButton command="tableStructure.reset" />
|
<ToolStripCommandButton command="tableStructure.reset" />
|
||||||
<ToolStripCommandButton command="tableEditor.addColumn" />
|
<ToolStripCommandButton command="tableEditor.addColumn" />
|
||||||
<ToolStripCommandButton command="tableEditor.addIndex" hideDisabled />
|
<ToolStripCommandButton command="tableEditor.addIndex" hideDisabled />
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function findFreeNumber(numbers: number[]) {
|
|||||||
// return res;
|
// return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function openNewTab(newTab, initialData = undefined, options = undefined) {
|
export default async function openNewTab(newTab, initialData: any = undefined, options: any = undefined) {
|
||||||
const oldTabs = getOpenedTabs();
|
const oldTabs = getOpenedTabs();
|
||||||
const activeTab = getActiveTab();
|
const activeTab = getActiveTab();
|
||||||
|
|
||||||
|
|||||||
139
packages/web/src/widgets/SchemaSelector.svelte
Normal file
139
packages/web/src/widgets/SchemaSelector.svelte
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import InlineButton from '../buttons/InlineButton.svelte';
|
||||||
|
import SelectField from '../forms/SelectField.svelte';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import { DatabaseInfo } from 'dbgate-types';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||||
|
import { runOperationOnDatabase } from '../modals/ConfirmSqlModal.svelte';
|
||||||
|
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||||
|
import { appliedCurrentSchema } from '../stores';
|
||||||
|
|
||||||
|
export let dbinfo: DatabaseInfo;
|
||||||
|
export let selectedSchema;
|
||||||
|
export let objectList;
|
||||||
|
|
||||||
|
export let valueStorageKey;
|
||||||
|
|
||||||
|
export let conid;
|
||||||
|
export let database;
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (selectedSchema != null) {
|
||||||
|
$appliedCurrentSchema = selectedSchema;
|
||||||
|
} else {
|
||||||
|
const usedSchemas = Object.keys(countBySchema);
|
||||||
|
if (usedSchemas.length == 1) {
|
||||||
|
$appliedCurrentSchema = usedSchemas[0];
|
||||||
|
} else {
|
||||||
|
$appliedCurrentSchema = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeCountBySchema(list) {
|
||||||
|
const res = {};
|
||||||
|
for (const item of list) {
|
||||||
|
if (!item.schemaName) continue;
|
||||||
|
if (!res[item.schemaName]) res[item.schemaName] = 0;
|
||||||
|
res[item.schemaName] += 1;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: schemaList = _.uniq(
|
||||||
|
_.compact([selectedSchema, ...Object.keys(countBySchema), ...(dbinfo?.schemas?.map(x => x.schemaName) ?? [])])
|
||||||
|
);
|
||||||
|
$: countBySchema = computeCountBySchema(objectList ?? []);
|
||||||
|
|
||||||
|
function handleCreateSchema() {
|
||||||
|
showModal(InputTextModal, {
|
||||||
|
header: 'Create schema',
|
||||||
|
value: 'newschema',
|
||||||
|
label: 'Schema name',
|
||||||
|
onConfirm: async name => {
|
||||||
|
const dbid = { conid, database };
|
||||||
|
await runOperationOnDatabase(dbid, {
|
||||||
|
type: 'createSchema',
|
||||||
|
schemaName: name,
|
||||||
|
});
|
||||||
|
if (selectedSchema) {
|
||||||
|
selectedSchema = name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function handleDropSchema() {
|
||||||
|
showModal(ConfirmModal, {
|
||||||
|
message: `Really drop schema ${$appliedCurrentSchema}?`,
|
||||||
|
onConfirm: async () => {
|
||||||
|
const dbid = { conid, database };
|
||||||
|
runOperationOnDatabase(dbid, {
|
||||||
|
type: 'dropSchema',
|
||||||
|
schemaName: $appliedCurrentSchema,
|
||||||
|
});
|
||||||
|
selectedSchema = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$: selectedSchema = localStorage.getItem(valueStorageKey ?? '');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if schemaList.length > 0}
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="mr-1">Schema:</div>
|
||||||
|
<SelectField
|
||||||
|
isNative
|
||||||
|
options={[
|
||||||
|
{ label: `All schemas (${objectList?.length ?? 0})`, value: '' },
|
||||||
|
...schemaList.map(x => ({ label: `${x} (${countBySchema[x] ?? 0})`, value: x })),
|
||||||
|
// ...schemaList.filter(x => countBySchema[x]).map(x => ({ label: `${x} (${countBySchema[x] ?? 0})`, value: x })),
|
||||||
|
// ...schemaList.filter(x => !countBySchema[x]).map(x => ({ label: `${x} (${countBySchema[x] ?? 0})`, value: x })),
|
||||||
|
]}
|
||||||
|
value={selectedSchema ?? $appliedCurrentSchema ?? ''}
|
||||||
|
on:change={e => {
|
||||||
|
selectedSchema = e.detail;
|
||||||
|
localStorage.setItem(valueStorageKey, e.detail);
|
||||||
|
}}
|
||||||
|
selectClass="schema-select"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if selectedSchema != null}
|
||||||
|
<InlineButton
|
||||||
|
on:click={() => {
|
||||||
|
selectedSchema = null;
|
||||||
|
}}
|
||||||
|
title="Reset to default"
|
||||||
|
>
|
||||||
|
<FontIcon icon="icon close" />
|
||||||
|
</InlineButton>
|
||||||
|
{/if}
|
||||||
|
<InlineButton on:click={handleCreateSchema} title="Add new schema" square>
|
||||||
|
<FontIcon icon="icon plus-thick" />
|
||||||
|
</InlineButton>
|
||||||
|
<InlineButton on:click={handleDropSchema} title="Delete schema" square disabled={!$appliedCurrentSchema}>
|
||||||
|
<FontIcon icon="icon minus-thick" />
|
||||||
|
</InlineButton>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--theme-border);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.schema-select) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 10px;
|
||||||
|
min-height: 22px;
|
||||||
|
width: 10px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -35,11 +35,14 @@
|
|||||||
import runCommand from '../commands/runCommand';
|
import runCommand from '../commands/runCommand';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import { filterAppsForDatabase } from '../utility/appTools';
|
import { filterAppsForDatabase } from '../utility/appTools';
|
||||||
|
import SchemaSelector from './SchemaSelector.svelte';
|
||||||
|
import { appliedCurrentSchema } from '../stores';
|
||||||
|
|
||||||
export let conid;
|
export let conid;
|
||||||
export let database;
|
export let database;
|
||||||
|
|
||||||
let filter = '';
|
let filter = '';
|
||||||
|
let selectedSchema = null;
|
||||||
|
|
||||||
$: objects = useDatabaseInfo({ conid, database });
|
$: objects = useDatabaseInfo({ conid, database });
|
||||||
$: status = useDatabaseStatus({ conid, database });
|
$: status = useDatabaseStatus({ conid, database });
|
||||||
@@ -99,6 +102,12 @@
|
|||||||
);
|
);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: flatFilteredList = objectList.filter(data => {
|
||||||
|
const matcher = databaseObjectAppObject.createMatcher(data);
|
||||||
|
if (matcher && !matcher(filter)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $status && $status.name == 'error'}
|
{#if $status && $status.name == 'error'}
|
||||||
@@ -130,16 +139,27 @@
|
|||||||
<SearchInput placeholder="Search in tables, objects, # prefix in columns" bind:value={filter} />
|
<SearchInput placeholder="Search in tables, objects, # prefix in columns" bind:value={filter} />
|
||||||
<CloseSearchButton bind:filter />
|
<CloseSearchButton bind:filter />
|
||||||
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
|
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
|
||||||
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list">
|
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square>
|
||||||
<FontIcon icon="icon refresh" />
|
<FontIcon icon="icon refresh" />
|
||||||
</InlineButton>
|
</InlineButton>
|
||||||
</SearchBoxWrapper>
|
</SearchBoxWrapper>
|
||||||
|
<SchemaSelector
|
||||||
|
dbinfo={$objects}
|
||||||
|
bind:selectedSchema
|
||||||
|
objectList={flatFilteredList}
|
||||||
|
valueStorageKey={`sql-object-list-schema-${conid}-${database}`}
|
||||||
|
{conid}
|
||||||
|
{database}
|
||||||
|
/>
|
||||||
|
|
||||||
<WidgetsInnerContainer>
|
<WidgetsInnerContainer>
|
||||||
{#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects}
|
{#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects}
|
||||||
<LoadingInfo message={$status?.feedback?.analysingMessage || 'Loading database structure'} />
|
<LoadingInfo message={$status?.feedback?.analysingMessage || 'Loading database structure'} />
|
||||||
{:else}
|
{:else}
|
||||||
<AppObjectList
|
<AppObjectList
|
||||||
list={objectList.map(x => ({ ...x, conid, database }))}
|
list={objectList
|
||||||
|
.filter(x => ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
|
||||||
|
.map(x => ({ ...x, conid, database }))}
|
||||||
module={databaseObjectAppObject}
|
module={databaseObjectAppObject}
|
||||||
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
|
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
|
||||||
subItemsComponent={SubColumnParamList}
|
subItemsComponent={SubColumnParamList}
|
||||||
@@ -147,7 +167,11 @@
|
|||||||
data.objectTypeField == 'tables' || data.objectTypeField == 'views' || data.objectTypeField == 'matviews'}
|
data.objectTypeField == 'tables' || data.objectTypeField == 'views' || data.objectTypeField == 'matviews'}
|
||||||
expandIconFunc={chevronExpandIcon}
|
expandIconFunc={chevronExpandIcon}
|
||||||
{filter}
|
{filter}
|
||||||
passProps={{ showPinnedInsteadOfUnpin: true, connection: $connection }}
|
passProps={{
|
||||||
|
showPinnedInsteadOfUnpin: true,
|
||||||
|
connection: $connection,
|
||||||
|
hideSchemaName: !!$appliedCurrentSchema,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</WidgetsInnerContainer>
|
</WidgetsInnerContainer>
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
|||||||
this.singleObjectId = resId.rows[0].id;
|
this.singleObjectId = resId.rows[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async readSchemaList() {
|
||||||
|
const schemaRows = await this.analyserQuery('getSchemas');
|
||||||
|
const schemas = schemaRows.rows;
|
||||||
|
return schemas;
|
||||||
|
}
|
||||||
|
|
||||||
async _runAnalysis() {
|
async _runAnalysis() {
|
||||||
this.feedback({ analysingMessage: 'Loading tables' });
|
this.feedback({ analysingMessage: 'Loading tables' });
|
||||||
const tablesRows = await this.analyserQuery('tables', ['tables']);
|
const tablesRows = await this.analyserQuery('tables', ['tables']);
|
||||||
@@ -88,8 +94,6 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
|||||||
const pkColumnsRows = await this.analyserQuery('primaryKeys', ['tables']);
|
const pkColumnsRows = await this.analyserQuery('primaryKeys', ['tables']);
|
||||||
this.feedback({ analysingMessage: 'Loading foreign keys' });
|
this.feedback({ analysingMessage: 'Loading foreign keys' });
|
||||||
const fkColumnsRows = await this.analyserQuery('foreignKeys', ['tables']);
|
const fkColumnsRows = await this.analyserQuery('foreignKeys', ['tables']);
|
||||||
this.feedback({ analysingMessage: 'Loading schemas' });
|
|
||||||
const schemaRows = await this.analyserQuery('getSchemas');
|
|
||||||
this.feedback({ analysingMessage: 'Loading indexes' });
|
this.feedback({ analysingMessage: 'Loading indexes' });
|
||||||
const indexesRows = await this.analyserQuery('indexes', ['tables']);
|
const indexesRows = await this.analyserQuery('indexes', ['tables']);
|
||||||
this.feedback({ analysingMessage: 'Loading index columns' });
|
this.feedback({ analysingMessage: 'Loading index columns' });
|
||||||
@@ -99,17 +103,10 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
|||||||
this.feedback({ analysingMessage: 'Loading table sizes' });
|
this.feedback({ analysingMessage: 'Loading table sizes' });
|
||||||
const tableSizes = await this.analyserQuery('tableSizes');
|
const tableSizes = await this.analyserQuery('tableSizes');
|
||||||
|
|
||||||
const schemas = schemaRows.rows;
|
|
||||||
|
|
||||||
const tableSizesDict = _.mapValues(_.keyBy(tableSizes.rows, 'objectId'), 'tableRowCount');
|
const tableSizesDict = _.mapValues(_.keyBy(tableSizes.rows, 'objectId'), 'tableRowCount');
|
||||||
|
|
||||||
this.feedback({ analysingMessage: 'Loading SQL code' });
|
this.feedback({ analysingMessage: 'Loading SQL code' });
|
||||||
const sqlCodeRows = await this.analyserQuery('loadSqlCode', [
|
const sqlCodeRows = await this.analyserQuery('loadSqlCode', ['views', 'procedures', 'functions', 'triggers']);
|
||||||
'views',
|
|
||||||
'procedures',
|
|
||||||
'functions',
|
|
||||||
'triggers',
|
|
||||||
]);
|
|
||||||
const getCreateSql = row =>
|
const getCreateSql = row =>
|
||||||
sqlCodeRows.rows
|
sqlCodeRows.rows
|
||||||
.filter(x => x.pureName == row.pureName && x.schemaName == row.schemaName)
|
.filter(x => x.pureName == row.pureName && x.schemaName == row.schemaName)
|
||||||
@@ -182,7 +179,6 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
|||||||
views,
|
views,
|
||||||
procedures,
|
procedures,
|
||||||
functions,
|
functions,
|
||||||
schemas,
|
|
||||||
defaultSchema: defaultSchemaRows.rows[0] ? defaultSchemaRows.rows[0].name : undefined,
|
defaultSchema: defaultSchemaRows.rows[0] ? defaultSchemaRows.rows[0].name : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,17 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async readSchemaList() {
|
||||||
|
const schemaRows = await this.analyserQuery('getSchemas');
|
||||||
|
|
||||||
|
const schemas = schemaRows.rows.map(x => ({
|
||||||
|
schemaName: x.schema_name,
|
||||||
|
objectId: `schemas:${x.schema_name}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return schemas;
|
||||||
|
}
|
||||||
|
|
||||||
async _getFastSnapshot() {
|
async _getFastSnapshot() {
|
||||||
const tableModificationsQueryData = this.driver.dialect.stringAgg
|
const tableModificationsQueryData = this.driver.dialect.stringAgg
|
||||||
? await this.analyserQuery('tableModifications')
|
? await this.analyserQuery('tableModifications')
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
module.exports = `select oid as "object_id", nspname as "schema_name" from pg_catalog.pg_namespace`;
|
||||||
@@ -12,6 +12,7 @@ const matviewColumns = require('./matviewColumns');
|
|||||||
const indexes = require('./indexes');
|
const indexes = require('./indexes');
|
||||||
const indexcols = require('./indexcols');
|
const indexcols = require('./indexcols');
|
||||||
const uniqueNames = require('./uniqueNames');
|
const uniqueNames = require('./uniqueNames');
|
||||||
|
const getSchemas = require('./getSchemas');
|
||||||
const geometryColumns = require('./geometryColumns');
|
const geometryColumns = require('./geometryColumns');
|
||||||
const geographyColumns = require('./geographyColumns');
|
const geographyColumns = require('./geographyColumns');
|
||||||
|
|
||||||
@@ -39,4 +40,5 @@ module.exports = {
|
|||||||
uniqueNames,
|
uniqueNames,
|
||||||
geometryColumns,
|
geometryColumns,
|
||||||
geographyColumns,
|
geographyColumns,
|
||||||
|
getSchemas,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user