schema update in database analyser

This commit is contained in:
SPRINX0\prochazka 2024-09-18 15:37:34 +02:00
parent 327d43096f
commit 5ab686b721
9 changed files with 101 additions and 22 deletions

View File

@ -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,20 +107,29 @@ 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 = {
...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()));
} }

View File

@ -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) {}

View File

@ -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;
} },
}; };

View File

@ -5,6 +5,10 @@
import _ from 'lodash'; import _ from 'lodash';
import FontIcon from '../icons/FontIcon.svelte'; import FontIcon from '../icons/FontIcon.svelte';
import { DatabaseInfo } from 'dbgate-types'; 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';
export let dbinfo: DatabaseInfo; export let dbinfo: DatabaseInfo;
export let selectedSchema; export let selectedSchema;
@ -13,6 +17,9 @@
export let onApplySelectedSchema; export let onApplySelectedSchema;
export let valueStorageKey; export let valueStorageKey;
export let conid;
export let database;
let appliedSchema; let appliedSchema;
$: { $: {
@ -45,8 +52,33 @@
); );
$: countBySchema = computeCountBySchema(objectList ?? []); $: countBySchema = computeCountBySchema(objectList ?? []);
function handleAddNewSchema() { function handleCreateSchema() {
// runCommand('add-schema', { conid: dbinfo.conid, database: dbinfo.database }); showModal(InputTextModal, {
header: 'Create schema',
value: 'newschema',
label: 'Schema name',
onConfirm: async name => {
const dbid = { conid, database };
await runOperationOnDatabase(dbid, {
type: 'createSchema',
schemaName: name,
});
selectedSchema = name;
},
});
}
function handleDropSchema() {
showModal(ConfirmModal, {
message: `Really drop schema ${appliedSchema}?`,
onConfirm: async () => {
const dbid = { conid, database };
runOperationOnDatabase(dbid, {
type: 'dropSchema',
schemaName: appliedSchema,
});
selectedSchema = null;
},
});
} }
$: selectedSchema = localStorage.getItem(valueStorageKey ?? ''); $: selectedSchema = localStorage.getItem(valueStorageKey ?? '');
@ -81,10 +113,10 @@
<FontIcon icon="icon close" /> <FontIcon icon="icon close" />
</InlineButton> </InlineButton>
{/if} {/if}
<InlineButton on:click={handleAddNewSchema} title="Add new schema" square> <InlineButton on:click={handleCreateSchema} title="Add new schema" square>
<FontIcon icon="icon plus-thick" /> <FontIcon icon="icon plus-thick" />
</InlineButton> </InlineButton>
<InlineButton on:click={handleAddNewSchema} title="Delete schema" square> <InlineButton on:click={handleDropSchema} title="Delete schema" square disabled={!appliedSchema}>
<FontIcon icon="icon minus-thick" /> <FontIcon icon="icon minus-thick" />
</InlineButton> </InlineButton>
</div> </div>

View File

@ -151,6 +151,8 @@
appliedSelectedSchema = x; appliedSelectedSchema = x;
}} }}
valueStorageKey={`sql-object-list-schema-${conid}-${database}`} valueStorageKey={`sql-object-list-schema-${conid}-${database}`}
{conid}
{database}
/> />
<WidgetsInnerContainer> <WidgetsInnerContainer>

View File

@ -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,
}; };
} }

View File

@ -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')

View File

@ -0,0 +1 @@
module.exports = `select oid as "object_id", nspname as "schema_name" from pg_catalog.pg_namespace`;

View File

@ -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,
}; };