diff --git a/packages/tools/src/DatabaseAnalyser.ts b/packages/tools/src/DatabaseAnalyser.ts index 255de678..b189a390 100644 --- a/packages/tools/src/DatabaseAnalyser.ts +++ b/packages/tools/src/DatabaseAnalyser.ts @@ -5,6 +5,7 @@ import _pick from 'lodash/pick'; import _compact from 'lodash/compact'; import { getLogger } from './getLogger'; import { type Logger } from 'pinomin'; +import stableStringify from 'json-stable-stringify'; const logger = getLogger('dbAnalyser'); @@ -70,7 +71,10 @@ export class DatabaseAnalyser { async fullAnalysis() { const res = this.addEngineField(await this._runAnalysis()); // console.log('FULL ANALYSIS', res); - return res; + return { + ...res, + schemas: await this.readSchemaList(), + }; } async singleObjectAnalysis(name, typeField) { @@ -87,6 +91,10 @@ export class DatabaseAnalyser { return obj; } + async readSchemaList() { + return undefined; + } + async incrementalAnalysis(structure) { this.structure = structure; @@ -99,20 +107,29 @@ export class DatabaseAnalyser { const structureModifications = modifications.filter(x => x.action != 'setTableRowCounts'); const setTableRowCounts = modifications.find(x => x.action == 'setTableRowCounts'); - let structureWithRowCounts = null; + let structureUpdated = null; if (setTableRowCounts) { const newStructure = mergeTableRowCounts(structure, setTableRowCounts.rowCounts); 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) { - return structureWithRowCounts ? this.addEngineField(structureWithRowCounts) : null; + return structureUpdated ? this.addEngineField(structureUpdated) : null; } this.modifications = structureModifications; - if (structureWithRowCounts) this.structure = structureWithRowCounts; + if (structureUpdated) this.structure = structureUpdated; logger.info({ modifications: this.modifications }, 'DB modifications detected:'); return this.addEngineField(this.mergeAnalyseResult(await this._runAnalysis())); } diff --git a/packages/tools/src/SqlDumper.ts b/packages/tools/src/SqlDumper.ts index 4e0582d3..3fe9e625 100644 --- a/packages/tools/src/SqlDumper.ts +++ b/packages/tools/src/SqlDumper.ts @@ -214,6 +214,14 @@ export class SqlDumper implements AlterProcessor { 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) {} selectScopeIdentity(table: TableInfo) {} diff --git a/packages/tools/src/driverBase.ts b/packages/tools/src/driverBase.ts index ebcf03cd..251ea87b 100644 --- a/packages/tools/src/driverBase.ts +++ b/packages/tools/src/driverBase.ts @@ -84,7 +84,17 @@ export const driverBase = { } }, 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() { if (this.databaseEngineTypes.includes('sql')) { @@ -180,5 +190,5 @@ export const driverBase = { adaptTableInfo(table) { return table; - } + }, }; diff --git a/packages/web/src/widgets/SchemaSelector.svelte b/packages/web/src/widgets/SchemaSelector.svelte index ffdc1a3e..bfb457e0 100644 --- a/packages/web/src/widgets/SchemaSelector.svelte +++ b/packages/web/src/widgets/SchemaSelector.svelte @@ -5,6 +5,10 @@ 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'; export let dbinfo: DatabaseInfo; export let selectedSchema; @@ -13,6 +17,9 @@ export let onApplySelectedSchema; export let valueStorageKey; + export let conid; + export let database; + let appliedSchema; $: { @@ -45,8 +52,33 @@ ); $: countBySchema = computeCountBySchema(objectList ?? []); - function handleAddNewSchema() { - // runCommand('add-schema', { conid: dbinfo.conid, database: dbinfo.database }); + 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, + }); + 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 ?? ''); @@ -81,10 +113,10 @@ {/if} - + - + diff --git a/packages/web/src/widgets/SqlObjectList.svelte b/packages/web/src/widgets/SqlObjectList.svelte index ce6c3475..7c2ec45f 100644 --- a/packages/web/src/widgets/SqlObjectList.svelte +++ b/packages/web/src/widgets/SqlObjectList.svelte @@ -151,6 +151,8 @@ appliedSelectedSchema = x; }} valueStorageKey={`sql-object-list-schema-${conid}-${database}`} + {conid} + {database} /> diff --git a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js index 3942b03c..75793391 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js +++ b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js @@ -79,6 +79,12 @@ class MsSqlAnalyser extends DatabaseAnalyser { this.singleObjectId = resId.rows[0].id; } + async readSchemaList() { + const schemaRows = await this.analyserQuery('getSchemas'); + const schemas = schemaRows.rows; + return schemas; + } + async _runAnalysis() { this.feedback({ analysingMessage: 'Loading tables' }); const tablesRows = await this.analyserQuery('tables', ['tables']); @@ -88,8 +94,6 @@ class MsSqlAnalyser extends DatabaseAnalyser { const pkColumnsRows = await this.analyserQuery('primaryKeys', ['tables']); this.feedback({ analysingMessage: 'Loading foreign keys' }); const fkColumnsRows = await this.analyserQuery('foreignKeys', ['tables']); - this.feedback({ analysingMessage: 'Loading schemas' }); - const schemaRows = await this.analyserQuery('getSchemas'); this.feedback({ analysingMessage: 'Loading indexes' }); const indexesRows = await this.analyserQuery('indexes', ['tables']); this.feedback({ analysingMessage: 'Loading index columns' }); @@ -99,17 +103,10 @@ class MsSqlAnalyser extends DatabaseAnalyser { this.feedback({ analysingMessage: 'Loading table sizes' }); const tableSizes = await this.analyserQuery('tableSizes'); - const schemas = schemaRows.rows; - const tableSizesDict = _.mapValues(_.keyBy(tableSizes.rows, 'objectId'), 'tableRowCount'); this.feedback({ analysingMessage: 'Loading SQL code' }); - const sqlCodeRows = await this.analyserQuery('loadSqlCode', [ - 'views', - 'procedures', - 'functions', - 'triggers', - ]); + const sqlCodeRows = await this.analyserQuery('loadSqlCode', ['views', 'procedures', 'functions', 'triggers']); const getCreateSql = row => sqlCodeRows.rows .filter(x => x.pureName == row.pureName && x.schemaName == row.schemaName) @@ -182,7 +179,6 @@ class MsSqlAnalyser extends DatabaseAnalyser { views, procedures, functions, - schemas, defaultSchema: defaultSchemaRows.rows[0] ? defaultSchemaRows.rows[0].name : undefined, }; } diff --git a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js index f5153af8..bca81994 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js @@ -312,6 +312,17 @@ class Analyser extends DatabaseAnalyser { 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() { const tableModificationsQueryData = this.driver.dialect.stringAgg ? await this.analyserQuery('tableModifications') diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/getSchemas.js b/plugins/dbgate-plugin-postgres/src/backend/sql/getSchemas.js new file mode 100644 index 00000000..88f8972d --- /dev/null +++ b/plugins/dbgate-plugin-postgres/src/backend/sql/getSchemas.js @@ -0,0 +1 @@ +module.exports = `select oid as "object_id", nspname as "schema_name" from pg_catalog.pg_namespace`; diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/index.js b/plugins/dbgate-plugin-postgres/src/backend/sql/index.js index 53a858ab..9fb45a93 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/sql/index.js +++ b/plugins/dbgate-plugin-postgres/src/backend/sql/index.js @@ -12,6 +12,7 @@ const matviewColumns = require('./matviewColumns'); const indexes = require('./indexes'); const indexcols = require('./indexcols'); const uniqueNames = require('./uniqueNames'); +const getSchemas = require('./getSchemas'); const geometryColumns = require('./geometryColumns'); const geographyColumns = require('./geographyColumns'); @@ -39,4 +40,5 @@ module.exports = { uniqueNames, geometryColumns, geographyColumns, + getSchemas, };