diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index f40c16b8e..7e9e96c55 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -1,15 +1,9 @@ -const _ = require('lodash'); -const fp = require('lodash/fp'); const uuidv1 = require('uuid/v1'); const connections = require('./connections'); const socket = require('../utility/socket'); const { fork } = require('child_process'); const DatabaseAnalyser = require('@dbgate/engines/default/DatabaseAnalyser'); -function pickObjectNames(array) { - return _.sortBy(array, (x) => `${x.schemaName}.${x.pureName}`).map(fp.pick(['pureName', 'schemaName'])); -} - module.exports = { /** @type {import('@dbgate/types').OpenedDatabaseConnection[]} */ opened: [], @@ -62,19 +56,6 @@ module.exports = { return promise; }, - listObjects_meta: 'get', - async listObjects({ conid, database }) { - const opened = await this.ensureOpened(conid, database); - const types = ['tables', 'views', 'procedures', 'functions', 'triggers']; - return types.reduce( - (res, type) => ({ - ...res, - [type]: pickObjectNames(opened.structure[type]), - }), - {} - ); - }, - queryData_meta: 'post', async queryData({ conid, database, sql }) { console.log(`Processing query, conid=${conid}, database=${database}, sql=${sql}`); diff --git a/packages/api/src/controllers/metadata.js b/packages/api/src/controllers/metadata.js new file mode 100644 index 000000000..08433b245 --- /dev/null +++ b/packages/api/src/controllers/metadata.js @@ -0,0 +1,47 @@ +const _ = require('lodash'); +const fp = require('lodash/fp'); +const databaseConnections = require('./databaseConnections'); + +function pickObjectNames(array) { + return _.sortBy(array, (x) => `${x.schemaName}.${x.pureName}`).map(fp.pick(['pureName', 'schemaName'])); +} + +module.exports = { + // tableData_meta: 'get', + // async tableData({ conid, database, schemaName, pureName }) { + // const opened = await databaseConnections.ensureOpened(conid, database); + // const res = await databaseConnections.sendRequest(opened, { msgtype: 'tableData', schemaName, pureName }); + // return res; + // }, + + listObjects_meta: 'get', + async listObjects({ conid, database }) { + const opened = await databaseConnections.ensureOpened(conid, database); + const types = ['tables', 'views', 'procedures', 'functions', 'triggers']; + return types.reduce( + (res, type) => ({ + ...res, + [type]: pickObjectNames(opened.structure[type]), + }), + {} + ); + }, + + tableInfo_meta: 'get', + async tableInfo({ conid, database, schemaName, pureName }) { + const opened = await databaseConnections.ensureOpened(conid, database); + const table = opened.structure.tables.find((x) => x.pureName == pureName && x.schemaName == schemaName); + const allForeignKeys = _.flatten(opened.structure.tables.map((x) => x.foreignKeys)); + return { + ...table, + dependencies: allForeignKeys.filter((x) => x.refSchemaName == schemaName && x.refTableName == pureName), + }; + }, + + viewInfo_meta: 'get', + async viewInfo({ conid, database, schemaName, pureName }) { + const opened = await databaseConnections.ensureOpened(conid, database); + const view = opened.structure.views.find((x) => x.pureName == pureName && x.schemaName == schemaName); + return view; + }, +}; diff --git a/packages/api/src/controllers/tables.js b/packages/api/src/controllers/tables.js deleted file mode 100644 index c679b8e1e..000000000 --- a/packages/api/src/controllers/tables.js +++ /dev/null @@ -1,22 +0,0 @@ -const _ = require('lodash'); -const databaseConnections = require('./databaseConnections'); - -module.exports = { - // tableData_meta: 'get', - // async tableData({ conid, database, schemaName, pureName }) { - // const opened = await databaseConnections.ensureOpened(conid, database); - // const res = await databaseConnections.sendRequest(opened, { msgtype: 'tableData', schemaName, pureName }); - // return res; - // }, - - tableInfo_meta: 'get', - async tableInfo({ conid, database, schemaName, pureName }) { - const opened = await databaseConnections.ensureOpened(conid, database); - const table = opened.structure.tables.find(x => x.pureName == pureName && x.schemaName == schemaName); - const allForeignKeys = _.flatten(opened.structure.tables.map(x => x.foreignKeys)); - return { - ...table, - dependencies: allForeignKeys.filter(x => x.refSchemaName == schemaName && x.refTableName == pureName), - }; - }, -}; diff --git a/packages/api/src/main.js b/packages/api/src/main.js index a7604c1a0..dbce87b0a 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -12,7 +12,7 @@ const socket = require('./utility/socket'); const connections = require('./controllers/connections'); const serverConnections = require('./controllers/serverConnections'); const databaseConnections = require('./controllers/databaseConnections'); -const tables = require('./controllers/tables'); +const metadata = require('./controllers/metadata'); const sessions = require('./controllers/sessions'); const jsldata = require('./controllers/jsldata'); @@ -30,7 +30,7 @@ function start(argument = null) { useController(app, '/connections', connections); useController(app, '/server-connections', serverConnections); useController(app, '/database-connections', databaseConnections); - useController(app, '/tables', tables); + useController(app, '/metadata', metadata); useController(app, '/sessions', sessions); useController(app, '/jsldata', jsldata); diff --git a/packages/datalib/src/ChangeSet.ts b/packages/datalib/src/ChangeSet.ts index 971f30eb1..3cb27415c 100644 --- a/packages/datalib/src/ChangeSet.ts +++ b/packages/datalib/src/ChangeSet.ts @@ -40,7 +40,7 @@ export function findExistingChangeSetItem( changeSet: ChangeSet, definition: ChangeSetRowDefinition ): [keyof ChangeSet, ChangeSetItem] { - if (!changeSet) return ['updates', null]; + if (!changeSet || !definition) return ['updates', null]; if (definition.insertedRowIndex != null) { return [ 'inserts', diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index 14e5bc956..33965ef02 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -1,10 +1,10 @@ import _ from 'lodash'; import { GridConfig, GridCache, GridConfigColumns } from './GridConfig'; -import { ForeignKeyInfo, TableInfo, ColumnInfo, DbType, EngineDriver } from '@dbgate/types'; +import { ForeignKeyInfo, TableInfo, ColumnInfo, DbType, EngineDriver, NamedObjectInfo } from '@dbgate/types'; import { parseFilter, getFilterType } from '@dbgate/filterparser'; import { filterName } from './filterName'; -import { Select, Expression } from '@dbgate/sqltree'; import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet'; +import { Expression, Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree'; export interface DisplayColumn { schemaName: string; @@ -46,12 +46,8 @@ export abstract class GridDisplay { protected setConfig: (config: GridConfig) => void, public cache: GridCache, protected setCache: ChangeCacheFunc, - protected getTableInfo: ({ schemaName, pureName }) => Promise, public driver?: EngineDriver ) {} - getPageQuery(offset: number, count: number): string { - return null; - } columns: DisplayColumn[]; baseTable?: TableInfo; changeSetKeyFields: string[] = null; @@ -113,39 +109,6 @@ export abstract class GridDisplay { return (this.config.hiddenColumns || []).map((x) => _.findIndex(this.columns, (y) => y.uniqueName == x)); } - enrichExpandedColumns(list: DisplayColumn[]): DisplayColumn[] { - const res = []; - for (const item of list) { - res.push(item); - if (this.isExpandedColumn(item.uniqueName)) res.push(...this.getExpandedColumns(item)); - } - return res; - } - - getExpandedColumns(column: DisplayColumn) { - const table = this.cache.tables[column.uniqueName]; - if (table) { - return this.enrichExpandedColumns(this.getDisplayColumns(table, column.uniquePath)); - } else { - // load expanded columns - this.requireFkTarget(column); - } - return []; - } - - requireFkTarget(column: DisplayColumn) { - const { uniqueName, foreignKey } = column; - this.getTableInfo({ schemaName: foreignKey.refSchemaName, pureName: foreignKey.refTableName }).then((table) => { - this.setCache((cache) => ({ - ...cache, - tables: { - ...cache.tables, - [uniqueName]: table, - }, - })); - }); - } - isColumnChecked(column: DisplayColumn) { // console.log('isColumnChecked', column, this.config.hiddenColumns); return column.uniquePath.length == 1 @@ -153,142 +116,6 @@ export abstract class GridDisplay { : this.config.addedColumns.includes(column.uniqueName); } - getDisplayColumn(table: TableInfo, col: ColumnInfo, parentPath: string[]) { - const uniquePath = [...parentPath, col.columnName]; - const uniqueName = uniquePath.join('.'); - // console.log('this.config.addedColumns', this.config.addedColumns, uniquePath); - return { - ...col, - pureName: table.pureName, - schemaName: table.schemaName, - headerText: uniquePath.length == 1 ? col.columnName : `${table.pureName}.${col.columnName}`, - uniqueName, - uniquePath, - isPrimaryKey: table.primaryKey && !!table.primaryKey.columns.find((x) => x.columnName == col.columnName), - foreignKey: - table.foreignKeys && - table.foreignKeys.find((fk) => fk.columns.length == 1 && fk.columns[0].columnName == col.columnName), - }; - } - - addAddedColumnsToSelect( - select: Select, - columns: DisplayColumn[], - parentAlias: string, - displayedColumnInfo: DisplayedColumnInfo - ): ReferenceActionResult { - let res: ReferenceActionResult = 'noAction'; - for (const column of columns) { - if (this.config.addedColumns.includes(column.uniqueName)) { - select.columns.push({ - exprType: 'column', - columnName: column.columnName, - alias: column.uniqueName, - source: { name: column, alias: parentAlias }, - }); - displayedColumnInfo[column.uniqueName] = { - ...column, - sourceAlias: parentAlias, - }; - res = 'refAdded'; - } - } - return res; - } - - addJoinsFromExpandedColumns( - select: Select, - columns: DisplayColumn[], - parentAlias: string, - columnSources - ): ReferenceActionResult { - let res: ReferenceActionResult = 'noAction'; - for (const column of columns) { - if (this.isExpandedColumn(column.uniqueName)) { - const table = this.cache.tables[column.uniqueName]; - if (table) { - const childAlias = `${column.uniqueName}_ref`; - const subcolumns = this.getDisplayColumns(table, column.uniquePath); - const tableAction = combineReferenceActions( - this.addJoinsFromExpandedColumns(select, subcolumns, childAlias, columnSources), - this.addAddedColumnsToSelect(select, subcolumns, childAlias, columnSources) - ); - - if (tableAction == 'refAdded') { - this.addReferenceToSelect(select, parentAlias, column); - res = 'refAdded'; - } - if (tableAction == 'loadRequired') { - return 'loadRequired'; - } - } else { - this.requireFkTarget(column); - res = 'loadRequired'; - } - } - } - return res; - // const addedColumns = this.getGridColumns().filter(x=>x.) - } - - addReferenceToSelect(select: Select, parentAlias: string, column: DisplayColumn) { - const childAlias = `${column.uniqueName}_ref`; - if ((select.from.relations || []).find((x) => x.alias == childAlias)) return; - const table = this.cache.tables[column.uniqueName]; - select.from.relations = [ - ...(select.from.relations || []), - { - joinType: 'LEFT JOIN', - name: table, - alias: childAlias, - conditions: [ - { - conditionType: 'binary', - operator: '=', - left: { - exprType: 'column', - columnName: column.columnName, - source: { name: column, alias: parentAlias }, - }, - right: { - exprType: 'column', - columnName: table.primaryKey.columns[0].columnName, - source: { name: table, alias: childAlias }, - }, - }, - ], - }, - ]; - } - - addHintsToSelect(select: Select): ReferenceActionResult { - let res: ReferenceActionResult = 'noAction'; - for (const column of this.getGridColumns()) { - if (column.foreignKey) { - const table = this.cache.tables[column.uniqueName]; - if (table) { - const hintColumn = table.columns.find((x) => x?.dataType?.toLowerCase()?.includes('char')); - if (hintColumn) { - const parentUniqueName = column.uniquePath.slice(0, -1).join('.'); - this.addReferenceToSelect(select, parentUniqueName ? `${parentUniqueName}_ref` : 'basetbl', column); - const childAlias = `${column.uniqueName}_ref`; - select.columns.push({ - exprType: 'column', - columnName: hintColumn.columnName, - alias: `hint_${column.uniqueName}`, - source: { alias: childAlias }, - }); - res = 'refAdded'; - } - } else { - this.requireFkTarget(column); - res = 'loadRequired'; - } - } - } - return res; - } - applyFilterOnSelect(select: Select, displayedColumnInfo: DisplayedColumnInfo) { for (const uniqueName in this.config.filters) { const filter = this.config.filters[uniqueName]; @@ -328,20 +155,8 @@ export abstract class GridDisplay { } } - getDisplayColumns(table: TableInfo, parentPath: string[]) { - return ( - table?.columns - ?.map((col) => this.getDisplayColumn(table, col, parentPath)) - ?.map((col) => ({ - ...col, - isChecked: this.isColumnChecked(col), - hintColumnName: col.foreignKey ? `hint_${col.uniqueName}` : null, - })) || [] - ); - } - getColumns(columnFilter) { - return this.enrichExpandedColumns(this.columns.filter((col) => filterName(columnFilter, col.columnName))); + return this.columns.filter((col) => filterName(columnFilter, col.columnName)); } getGridColumns() { @@ -421,4 +236,54 @@ export abstract class GridDisplay { condition: insertedRowIndex == null ? this.getChangeSetCondition(row) : null, }; } + + createSelect(): Select { + return null; + } + + processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo): ReferenceActionResult { + return 'noAction'; + } + + createSelectBase(name: NamedObjectInfo, columns: ColumnInfo[]) { + if (!columns) return null; + const orderColumnName = columns[0].columnName; + const select: Select = { + commandType: 'select', + from: { name, alias: 'basetbl' }, + columns: columns.map((col) => ({ + exprType: 'column', + alias: col.columnName, + source: { alias: 'basetbl' }, + ...col, + })), + orderBy: [ + { + exprType: 'column', + columnName: orderColumnName, + direction: 'ASC', + }, + ], + }; + const displayedColumnInfo = _.keyBy( + this.columns.map((col) => ({ ...col, sourceAlias: 'basetbl' })), + 'uniqueName' + ); + const action = this.processReferences(select, displayedColumnInfo) + this.applyFilterOnSelect(select, displayedColumnInfo); + this.applySortOnSelect(select, displayedColumnInfo); + if (action == 'loadRequired') { + return null; + } + return select; + } + + getPageQuery(offset: number, count: number) { + const select = this.createSelect(); + if (!select) return null; + if (this.driver.dialect.rangeSelect) select.range = { offset: offset, limit: count }; + else if (this.driver.dialect.limitSelect) select.topRecords = count; + const sql = treeToSql(this.driver, select, dumpSqlSelect); + return sql; + } } diff --git a/packages/datalib/src/TableGridDisplay.ts b/packages/datalib/src/TableGridDisplay.ts index cf9dcd6e7..dc533bdac 100644 --- a/packages/datalib/src/TableGridDisplay.ts +++ b/packages/datalib/src/TableGridDisplay.ts @@ -1,8 +1,16 @@ import _ from 'lodash'; -import { GridDisplay, combineReferenceActions, ChangeCacheFunc } from './GridDisplay'; -import { Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree'; -import { TableInfo, EngineDriver } from '@dbgate/types'; +import { + GridDisplay, + combineReferenceActions, + ChangeCacheFunc, + DisplayColumn, + ReferenceActionResult, + DisplayedColumnInfo, +} from './GridDisplay'; +import { TableInfo, EngineDriver, ViewInfo, ColumnInfo } from '@dbgate/types'; import { GridConfig, GridCache } from './GridConfig'; +import { Expression, Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree'; +import { filterName } from './filterName'; export class TableGridDisplay extends GridDisplay { constructor( @@ -12,9 +20,9 @@ export class TableGridDisplay extends GridDisplay { setConfig: (config: GridConfig) => void, cache: GridCache, setCache: ChangeCacheFunc, - getTableInfo: ({ schemaName, pureName }) => Promise + protected getTableInfo: ({ schemaName, pureName }) => Promise ) { - super(config, setConfig, cache, setCache, getTableInfo, driver); + super(config, setConfig, cache, setCache, driver); this.columns = this.getDisplayColumns(table, []); this.filterable = true; this.sortable = true; @@ -27,48 +35,202 @@ export class TableGridDisplay extends GridDisplay { } } - createSelect() { - if (!this.table.columns) return null; - const orderColumnName = this.table.columns[0].columnName; - const select: Select = { - commandType: 'select', - from: { name: this.table, alias: 'basetbl' }, - columns: this.table.columns.map((col) => ({ - exprType: 'column', - alias: col.columnName, - source: { alias: 'basetbl' }, - ...col, - })), - orderBy: [ - { - exprType: 'column', - columnName: orderColumnName, - direction: 'ASC', - }, - ], - }; - const displayedColumnInfo = _.keyBy( - this.columns.map((col) => ({ ...col, sourceAlias: 'basetbl' })), - 'uniqueName' + getDisplayColumns(table: TableInfo, parentPath: string[]) { + return ( + table?.columns + ?.map((col) => this.getDisplayColumn(table, col, parentPath)) + ?.map((col) => ({ + ...col, + isChecked: this.isColumnChecked(col), + hintColumnName: col.foreignKey ? `hint_${col.uniqueName}` : null, + })) || [] ); + } + + addJoinsFromExpandedColumns( + select: Select, + columns: DisplayColumn[], + parentAlias: string, + columnSources + ): ReferenceActionResult { + let res: ReferenceActionResult = 'noAction'; + for (const column of columns) { + if (this.isExpandedColumn(column.uniqueName)) { + const table = this.cache.tables[column.uniqueName]; + if (table) { + const childAlias = `${column.uniqueName}_ref`; + const subcolumns = this.getDisplayColumns(table, column.uniquePath); + const tableAction = combineReferenceActions( + this.addJoinsFromExpandedColumns(select, subcolumns, childAlias, columnSources), + this.addAddedColumnsToSelect(select, subcolumns, childAlias, columnSources) + ); + + if (tableAction == 'refAdded') { + this.addReferenceToSelect(select, parentAlias, column); + res = 'refAdded'; + } + if (tableAction == 'loadRequired') { + return 'loadRequired'; + } + } else { + this.requireFkTarget(column); + res = 'loadRequired'; + } + } + } + return res; + // const addedColumns = this.getGridColumns().filter(x=>x.) + } + + addReferenceToSelect(select: Select, parentAlias: string, column: DisplayColumn) { + const childAlias = `${column.uniqueName}_ref`; + if ((select.from.relations || []).find((x) => x.alias == childAlias)) return; + const table = this.cache.tables[column.uniqueName]; + select.from.relations = [ + ...(select.from.relations || []), + { + joinType: 'LEFT JOIN', + name: table, + alias: childAlias, + conditions: [ + { + conditionType: 'binary', + operator: '=', + left: { + exprType: 'column', + columnName: column.columnName, + source: { name: column, alias: parentAlias }, + }, + right: { + exprType: 'column', + columnName: table.primaryKey.columns[0].columnName, + source: { name: table, alias: childAlias }, + }, + }, + ], + }, + ]; + } + + addHintsToSelect(select: Select): ReferenceActionResult { + let res: ReferenceActionResult = 'noAction'; + for (const column of this.getGridColumns()) { + if (column.foreignKey) { + const table = this.cache.tables[column.uniqueName]; + if (table) { + const hintColumn = table.columns.find((x) => x?.dataType?.toLowerCase()?.includes('char')); + if (hintColumn) { + const parentUniqueName = column.uniquePath.slice(0, -1).join('.'); + this.addReferenceToSelect(select, parentUniqueName ? `${parentUniqueName}_ref` : 'basetbl', column); + const childAlias = `${column.uniqueName}_ref`; + select.columns.push({ + exprType: 'column', + columnName: hintColumn.columnName, + alias: `hint_${column.uniqueName}`, + source: { alias: childAlias }, + }); + res = 'refAdded'; + } + } else { + this.requireFkTarget(column); + res = 'loadRequired'; + } + } + } + return res; + } + + enrichExpandedColumns(list: DisplayColumn[]): DisplayColumn[] { + const res = []; + for (const item of list) { + res.push(item); + if (this.isExpandedColumn(item.uniqueName)) res.push(...this.getExpandedColumns(item)); + } + return res; + } + + getExpandedColumns(column: DisplayColumn) { + const table = this.cache.tables[column.uniqueName]; + if (table) { + return this.enrichExpandedColumns(this.getDisplayColumns(table, column.uniquePath)); + } else { + // load expanded columns + this.requireFkTarget(column); + } + return []; + } + + requireFkTarget(column: DisplayColumn) { + const { uniqueName, foreignKey } = column; + this.getTableInfo({ schemaName: foreignKey.refSchemaName, pureName: foreignKey.refTableName }).then((table) => { + this.setCache((cache) => ({ + ...cache, + tables: { + ...cache.tables, + [uniqueName]: table, + }, + })); + }); + } + + + processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo): ReferenceActionResult { const action = combineReferenceActions( this.addJoinsFromExpandedColumns(select, this.columns, 'basetbl', displayedColumnInfo), this.addHintsToSelect(select) ); - this.applyFilterOnSelect(select, displayedColumnInfo); - this.applySortOnSelect(select, displayedColumnInfo); - if (action == 'loadRequired') { - return null; - } + return action; + } + + createSelect() { + const select = this.createSelectBase(this.table, this.table.columns); return select; } - getPageQuery(offset: number, count: number) { - const select = this.createSelect(); - if (!select) return null; - if (this.driver.dialect.rangeSelect) select.range = { offset: offset, limit: count }; - else if (this.driver.dialect.limitSelect) select.topRecords = count; - const sql = treeToSql(this.driver, select, dumpSqlSelect); - return sql; + getColumns(columnFilter) { + return this.enrichExpandedColumns(this.columns.filter((col) => filterName(columnFilter, col.columnName))); + } + + getDisplayColumn(table: TableInfo, col: ColumnInfo, parentPath: string[]) { + const uniquePath = [...parentPath, col.columnName]; + const uniqueName = uniquePath.join('.'); + // console.log('this.config.addedColumns', this.config.addedColumns, uniquePath); + return { + ...col, + pureName: table.pureName, + schemaName: table.schemaName, + headerText: uniquePath.length == 1 ? col.columnName : `${table.pureName}.${col.columnName}`, + uniqueName, + uniquePath, + isPrimaryKey: table.primaryKey && !!table.primaryKey.columns.find((x) => x.columnName == col.columnName), + foreignKey: + table.foreignKeys && + table.foreignKeys.find((fk) => fk.columns.length == 1 && fk.columns[0].columnName == col.columnName), + }; + } + + addAddedColumnsToSelect( + select: Select, + columns: DisplayColumn[], + parentAlias: string, + displayedColumnInfo: DisplayedColumnInfo + ): ReferenceActionResult { + let res: ReferenceActionResult = 'noAction'; + for (const column of columns) { + if (this.config.addedColumns.includes(column.uniqueName)) { + select.columns.push({ + exprType: 'column', + columnName: column.columnName, + alias: column.uniqueName, + source: { name: column, alias: parentAlias }, + }); + displayedColumnInfo[column.uniqueName] = { + ...column, + sourceAlias: parentAlias, + }; + res = 'refAdded'; + } + } + return res; } } diff --git a/packages/datalib/src/ViewGridDisplay.ts b/packages/datalib/src/ViewGridDisplay.ts new file mode 100644 index 000000000..ee7cf084b --- /dev/null +++ b/packages/datalib/src/ViewGridDisplay.ts @@ -0,0 +1,50 @@ +import _ from 'lodash'; +import { GridDisplay, ChangeCacheFunc } from './GridDisplay'; +import { EngineDriver, ViewInfo, ColumnInfo } from '@dbgate/types'; +import { GridConfig, GridCache } from './GridConfig'; + +export class ViewGridDisplay extends GridDisplay { + constructor( + public view: ViewInfo, + driver: EngineDriver, + config: GridConfig, + setConfig: (config: GridConfig) => void, + cache: GridCache, + setCache: ChangeCacheFunc + ) { + super(config, setConfig, cache, setCache, driver); + this.columns = this.getDisplayColumns(view); + this.filterable = true; + this.sortable = true; + this.editable = true; + } + + getDisplayColumns(view: ViewInfo) { + return ( + view?.columns + ?.map((col) => this.getDisplayColumn(view, col)) + ?.map((col) => ({ + ...col, + isChecked: this.isColumnChecked(col), + })) || [] + ); + } + + getDisplayColumn(view: ViewInfo, col: ColumnInfo) { + const uniquePath = [col.columnName]; + const uniqueName = uniquePath.join('.'); + return { + ...col, + pureName: view.pureName, + schemaName: view.schemaName, + headerText: col.columnName, + uniqueName, + uniquePath, + }; + } + + createSelect() { + const select = this.createSelectBase(this.view, this.view.columns); + return select; + } +} diff --git a/packages/datalib/src/index.ts b/packages/datalib/src/index.ts index f350abaec..96252dcce 100644 --- a/packages/datalib/src/index.ts +++ b/packages/datalib/src/index.ts @@ -1,6 +1,7 @@ export * from "./GridDisplay"; export * from "./GridConfig"; export * from "./TableGridDisplay"; +export * from "./ViewGridDisplay"; export * from "./JslGridDisplay"; export * from "./ChangeSet"; export * from "./filterName"; diff --git a/packages/engines/mssql/MsSqlAnalyser.js b/packages/engines/mssql/MsSqlAnalyser.js index 8bed1262a..6ff2fa332 100644 --- a/packages/engines/mssql/MsSqlAnalyser.js +++ b/packages/engines/mssql/MsSqlAnalyser.js @@ -227,6 +227,7 @@ class MsSqlAnalyser extends DatabaseAnalayser { this.pool, this.createQuery('programmables', ['procedures', 'functions']) ); + const viewColumnRows = await this.driver.query(this.pool, this.createQuery('viewColumns', ['views'])); const tables = tablesRows.rows.map((row) => ({ ...row, @@ -245,6 +246,14 @@ class MsSqlAnalyser extends DatabaseAnalayser { const views = viewsRows.rows.map((row) => ({ ...row, createSql: getCreateSql(row), + columns: viewColumnRows.rows + .filter((col) => (col.objectId = row.objectId)) + .map(({ isNullable, isIdentity, ...col }) => ({ + ...col, + notNull: !isNullable, + autoIncrement: !!isIdentity, + commonType: detectType(col), + })), })); const procedures = programmableRows.rows diff --git a/packages/engines/mssql/sql/index.js b/packages/engines/mssql/sql/index.js index cd99b326f..6165eaee1 100644 --- a/packages/engines/mssql/sql/index.js +++ b/packages/engines/mssql/sql/index.js @@ -6,6 +6,7 @@ const modifications = require('./modifications'); const loadSqlCode = require('./loadSqlCode'); const views = require('./views'); const programmables = require('./programmables'); +const viewColumns = require('./viewColumns'); module.exports = { columns, @@ -16,4 +17,5 @@ module.exports = { loadSqlCode, views, programmables, + viewColumns, }; diff --git a/packages/engines/mssql/sql/viewColumns.js b/packages/engines/mssql/sql/viewColumns.js new file mode 100644 index 000000000..a13fa8a0d --- /dev/null +++ b/packages/engines/mssql/sql/viewColumns.js @@ -0,0 +1,18 @@ +module.exports = ` +select + o.object_id AS objectId, + col.TABLE_SCHEMA as schemaName, + col.TABLE_NAME as pureName, + col.COLUMN_NAME as columnName, + col.IS_NULLABLE as isNullable, + col.DATA_TYPE as dataType, + col.CHARACTER_MAXIMUM_LENGTH, + col.NUMERIC_PRECISION as precision, + col.NUMERIC_SCALE as scale, + col.COLUMN_DEFAULT +FROM sys.objects o +INNER JOIN sys.schemas u ON u.schema_id=o.schema_id +INNER JOIN INFORMATION_SCHEMA.COLUMNS col ON col.TABLE_NAME = o.name AND col.TABLE_SCHEMA = u.name +WHERE o.type in ('V') and o.object_id =[OBJECT_ID_CONDITION] +order by col.ORDINAL_POSITION +`; diff --git a/packages/types/dbinfo.d.ts b/packages/types/dbinfo.d.ts index e8b280b3a..4f174c761 100644 --- a/packages/types/dbinfo.d.ts +++ b/packages/types/dbinfo.d.ts @@ -61,7 +61,9 @@ export interface TableInfo extends DatabaseObjectInfo { dependencies?: ForeignKeyInfo[]; } -export interface ViewInfo extends SqlObjectInfo {} +export interface ViewInfo extends SqlObjectInfo { + columns: ColumnInfo[]; +} export interface ProcedureInfo extends SqlObjectInfo {} diff --git a/packages/web/src/appobj/viewAppObject.js b/packages/web/src/appobj/viewAppObject.js index 706043172..9f06e8f80 100644 --- a/packages/web/src/appobj/viewAppObject.js +++ b/packages/web/src/appobj/viewAppObject.js @@ -6,58 +6,54 @@ import getConnectionInfo from '../utility/getConnectionInfo'; import fullDisplayName from '../utility/fullDisplayName'; import { filterName } from '@dbgate/datalib'; -// async function openTableDetail(setOpenedTabs, tabComponent, { schemaName, pureName, conid, database }) { -// const connection = await getConnectionInfo(conid); -// const tooltip = `${connection.displayName || connection.server}\n${database}\n${fullDisplayName({ -// schemaName, -// pureName, -// })}`; +async function openViewDetail(setOpenedTabs, tabComponent, { schemaName, pureName, conid, database }) { + const connection = await getConnectionInfo(conid); + const tooltip = `${connection.displayName || connection.server}\n${database}\n${fullDisplayName({ + schemaName, + pureName, + })}`; -// openNewTab(setOpenedTabs, { -// title: pureName, -// tooltip, -// icon: 'table2.svg', -// tabComponent, -// props: { -// schemaName, -// pureName, -// conid, -// database, -// }, -// }); -// } + openNewTab(setOpenedTabs, { + title: pureName, + tooltip, + icon: 'view2.svg', + tabComponent, + props: { + schemaName, + pureName, + conid, + database, + }, + }); +} -// function Menu({ data, makeAppObj, setOpenedTabs }) { -// const handleOpenData = () => { -// openTableDetail(setOpenedTabs, 'TableDataTab', data); -// }; -// const handleOpenStructure = () => { -// openTableDetail(setOpenedTabs, 'TableStructureTab', data); -// }; -// const handleOpenCreateScript = () => { -// openTableDetail(setOpenedTabs, 'TableCreateScriptTab', data); -// }; -// return ( -// <> -// Open data -// Open structure -// Create SQL -// -// ); -// } +function Menu({ data, makeAppObj, setOpenedTabs }) { + const handleOpenData = () => { + openViewDetail(setOpenedTabs, 'TableDataTab', data); + }; + const handleOpenCreateScript = () => { + openViewDetail(setOpenedTabs, 'TableCreateScriptTab', data); + }; + return ( + <> + Open data + Create SQL + + ); +} const viewAppObject = () => ({ conid, database, pureName, schemaName }, { setOpenedTabs }) => { const title = schemaName ? `${schemaName}.${pureName}` : pureName; const key = title; const Icon = ViewIcon; - // const onClick = ({ schemaName, pureName }) => { - // openTableDetail(setOpenedTabs, 'TableDataTab', { - // schemaName, - // pureName, - // conid, - // database, - // }); - // }; + const onClick = ({ schemaName, pureName }) => { + openViewDetail(setOpenedTabs, 'ViewDataTab', { + schemaName, + pureName, + conid, + database, + }); + }; const matcher = (filter) => filterName(filter, pureName); const groupTitle = 'Views'; @@ -65,7 +61,8 @@ const viewAppObject = () => ({ conid, database, pureName, schemaName }, { setOpe title, key, Icon, - // Menu, onClick, + Menu, + onClick, matcher, groupTitle, }; diff --git a/packages/web/src/tabs/ViewDataTab.js b/packages/web/src/tabs/ViewDataTab.js new file mode 100644 index 000000000..82d57bdbf --- /dev/null +++ b/packages/web/src/tabs/ViewDataTab.js @@ -0,0 +1,50 @@ +import React from 'react'; +import useFetch from '../utility/useFetch'; +import styled from 'styled-components'; +import theme from '../theme'; +import DataGrid from '../datagrid/DataGrid'; +import { ViewGridDisplay, createGridConfig, createGridCache, createChangeSet } from '@dbgate/datalib'; +import useTableInfo from '../utility/useTableInfo'; +import useConnectionInfo from '../utility/useConnectionInfo'; +import engines from '@dbgate/engines'; +import getTableInfo from '../utility/getTableInfo'; +import useUndoReducer from '../utility/useUndoReducer'; +import usePropsCompare from '../utility/usePropsCompare'; +import { useUpdateDatabaseForTab } from '../utility/globalState'; +import useViewInfo from '../utility/useViewInfo'; + +export default function ViewDataTab({ conid, database, schemaName, pureName, tabVisible, toolbarPortalRef }) { + const viewInfo = useViewInfo({ conid, database, schemaName, pureName }); + const [config, setConfig] = React.useState(createGridConfig()); + const [cache, setCache] = React.useState(createGridCache()); + const [changeSetState, dispatchChangeSet] = useUndoReducer(createChangeSet()); + + useUpdateDatabaseForTab(tabVisible, conid, database); + const connection = useConnectionInfo(conid); + + // usePropsCompare({ tableInfo, connection, config, cache }); + + const display = React.useMemo( + () => + viewInfo && connection + ? new ViewGridDisplay(viewInfo, engines(connection), config, setConfig, cache, setCache, + ) + : null, + [viewInfo, connection, config, cache] + ); + + if (!display) return null; + + return ( + + ); +} diff --git a/packages/web/src/tabs/index.js b/packages/web/src/tabs/index.js index a7a2f88dd..66eb2fb17 100644 --- a/packages/web/src/tabs/index.js +++ b/packages/web/src/tabs/index.js @@ -1,10 +1,12 @@ import TableDataTab from './TableDataTab'; +import ViewDataTab from './ViewDataTab'; import TableStructureTab from './TableStructureTab'; import TableCreateScriptTab from './TableCreateScriptTab'; import QueryTab from './QueryTab'; export default { TableDataTab, + ViewDataTab, TableStructureTab, TableCreateScriptTab, QueryTab, diff --git a/packages/web/src/utility/getTableInfo.js b/packages/web/src/utility/getTableInfo.js index ecec721c8..f81194cfa 100644 --- a/packages/web/src/utility/getTableInfo.js +++ b/packages/web/src/utility/getTableInfo.js @@ -3,7 +3,7 @@ import axios from './axios'; export default async function getTableInfo({ conid, database, schemaName, pureName }) { const resp = await axios.request({ method: 'get', - url: 'tables/table-info', + url: 'metadata/table-info', params: { conid, database, schemaName, pureName }, }); /** @type {import('@dbgate/types').TableInfo} */ diff --git a/packages/web/src/utility/useTableInfo.js b/packages/web/src/utility/useTableInfo.js index 13d577e4d..07c6a17b3 100644 --- a/packages/web/src/utility/useTableInfo.js +++ b/packages/web/src/utility/useTableInfo.js @@ -3,7 +3,7 @@ import useFetch from './useFetch'; export default function useTableInfo({ conid, database, schemaName, pureName }) { /** @type {import('@dbgate/types').TableInfo} */ const tableInfo = useFetch({ - url: 'tables/table-info', + url: 'metadata/table-info', params: { conid, database, schemaName, pureName }, reloadTrigger: `database-structure-changed-${conid}-${database}`, }); diff --git a/packages/web/src/utility/useViewInfo.js b/packages/web/src/utility/useViewInfo.js new file mode 100644 index 000000000..4d5120252 --- /dev/null +++ b/packages/web/src/utility/useViewInfo.js @@ -0,0 +1,11 @@ +import useFetch from './useFetch'; + +export default function useViewInfo({ conid, database, schemaName, pureName }) { + /** @type {import('@dbgate/types').ViewInfo} */ + const viewInfo = useFetch({ + url: 'metadata/view-info', + params: { conid, database, schemaName, pureName }, + reloadTrigger: `database-structure-changed-${conid}-${database}`, + }); + return viewInfo; +} diff --git a/packages/web/src/widgets/DatabaseWidget.js b/packages/web/src/widgets/DatabaseWidget.js index 3dd02f2fb..f6386cf87 100644 --- a/packages/web/src/widgets/DatabaseWidget.js +++ b/packages/web/src/widgets/DatabaseWidget.js @@ -95,7 +95,7 @@ function ConnectionList() { function SqlObjectList({ conid, database }) { const objects = useFetch({ - url: `database-connections/list-objects?conid=${conid}&database=${database}`, + url: `metadata/list-objects?conid=${conid}&database=${database}`, reloadTrigger: `database-structure-changed-${conid}-${database}`, });