diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index 2b32f8b6..c939ad51 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -129,6 +129,18 @@ module.exports = { } }, + structure_meta: 'get', + async structure({ conid, database }) { + const opened = await this.ensureOpened(conid, database); + return opened.structure; + // const existing = this.opened.find((x) => x.conid == conid && x.database == database); + // if (existing) return existing.status; + // return { + // name: 'error', + // message: 'Not connected', + // }; + }, + // runCommand_meta: 'post', // async runCommand({ conid, database, sql }) { // console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`); diff --git a/packages/datalib/src/GridConfig.ts b/packages/datalib/src/GridConfig.ts index 27093858..3f054a7e 100644 --- a/packages/datalib/src/GridConfig.ts +++ b/packages/datalib/src/GridConfig.ts @@ -27,8 +27,6 @@ export interface GridConfig extends GridConfigColumns { } export interface GridCache { - tables: { [uniqueName: string]: TableInfo }; - loadingTables: { schemaName: string; pureName: string }[]; refreshTime: number; } @@ -46,8 +44,6 @@ export function createGridConfig(): GridConfig { export function createGridCache(): GridCache { return { - tables: {}, - loadingTables: [], refreshTime: new Date().getTime(), }; } diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index eb0018bc..143612d1 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -30,13 +30,13 @@ export interface DisplayedColumnInfo { [uniqueName: string]: DisplayedColumnEx; } -export type ReferenceActionResult = 'noAction' | 'loadRequired' | 'refAdded'; +// export type ReferenceActionResult = 'noAction' | 'loadRequired' | 'refAdded'; -export function combineReferenceActions(a: ReferenceActionResult, b: ReferenceActionResult): ReferenceActionResult { - if (a == 'loadRequired' || b == 'loadRequired') return 'loadRequired'; - if (a == 'refAdded' || b == 'refAdded') return 'refAdded'; - return 'noAction'; -} +// export function combineReferenceActions(a: ReferenceActionResult, b: ReferenceActionResult): ReferenceActionResult { +// if (a == 'loadRequired' || b == 'loadRequired') return 'loadRequired'; +// if (a == 'refAdded' || b == 'refAdded') return 'refAdded'; +// return 'noAction'; +// } export type ChangeCacheFunc = (changeFunc: (cache: GridCache) => GridCache) => void; export type ChangeConfigFunc = (changeFunc: (config: GridConfig) => GridConfig) => void; @@ -269,9 +269,7 @@ export abstract class GridDisplay { return null; } - processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo): ReferenceActionResult { - return 'noAction'; - } + processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo) {} createSelectBase(name: NamedObjectInfo, columns: ColumnInfo[]) { if (!columns) return null; @@ -297,12 +295,9 @@ export abstract class GridDisplay { this.columns.map((col) => ({ ...col, sourceAlias: 'basetbl' })), 'uniqueName' ); - const action = this.processReferences(select, displayedColumnInfo); + this.processReferences(select, displayedColumnInfo); this.applyFilterOnSelect(select, displayedColumnInfo); this.applySortOnSelect(select, displayedColumnInfo); - if (action == 'loadRequired') { - return null; - } return select; } diff --git a/packages/datalib/src/TableGridDisplay.ts b/packages/datalib/src/TableGridDisplay.ts index 1770d485..a6f6a653 100644 --- a/packages/datalib/src/TableGridDisplay.ts +++ b/packages/datalib/src/TableGridDisplay.ts @@ -1,14 +1,6 @@ import _ from 'lodash'; -import { - GridDisplay, - combineReferenceActions, - ChangeCacheFunc, - DisplayColumn, - ReferenceActionResult, - DisplayedColumnInfo, - ChangeConfigFunc, -} from './GridDisplay'; -import { TableInfo, EngineDriver, ViewInfo, ColumnInfo, NamedObjectInfo } from '@dbgate/types'; +import { GridDisplay, ChangeCacheFunc, DisplayColumn, DisplayedColumnInfo, ChangeConfigFunc } from './GridDisplay'; +import { TableInfo, EngineDriver, ViewInfo, ColumnInfo, NamedObjectInfo, DatabaseInfo } from '@dbgate/types'; import { GridConfig, GridCache, createGridCache } from './GridConfig'; import { Expression, Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree'; import { filterName } from './filterName'; @@ -23,14 +15,12 @@ export class TableGridDisplay extends GridDisplay { setConfig: ChangeConfigFunc, cache: GridCache, setCache: ChangeCacheFunc, - protected getTableInfo: ({ schemaName, pureName }) => Promise + protected dbinfo: DatabaseInfo ) { super(config, setConfig, cache, setCache, driver); - const baseTblCacheKey = `basetbl_${tableName.schemaName}_${tableName.pureName}`; - this.table = this.cache.tables[baseTblCacheKey]; + this.table = this.findTable(tableName); if (!this.table) { - this.loadTableIntoCache(baseTblCacheKey, tableName); this.isLoadedCorrectly = false; } else { if (!this.table.columns || this.table.columns.length == 0) { @@ -50,6 +40,14 @@ export class TableGridDisplay extends GridDisplay { } } + findTable({ schemaName, pureName }) { + return ( + this.dbinfo && + this.dbinfo.tables && + this.dbinfo.tables.find((x) => x.pureName == pureName && x.schemaName == schemaName) + ); + } + getDisplayColumns(table: TableInfo, parentPath: string[]) { return ( table?.columns @@ -62,34 +60,23 @@ export class TableGridDisplay extends GridDisplay { ); } - addJoinsFromExpandedColumns( - select: Select, - columns: DisplayColumn[], - parentAlias: string, - columnSources - ): ReferenceActionResult { - let res: ReferenceActionResult = 'noAction'; + addJoinsFromExpandedColumns(select: Select, columns: DisplayColumn[], parentAlias: string, columnSources): boolean { + let res = false; for (const column of columns) { if (this.isExpandedColumn(column.uniqueName)) { - const table = this.cache.tables[column.uniqueName]; + const table = this.getFkTarget(column); 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') { + let added = false; + if (this.addJoinsFromExpandedColumns(select, subcolumns, childAlias, columnSources)) added = true; + if (this.addAddedColumnsToSelect(select, subcolumns, childAlias, columnSources)) added = true; + + if (added) { this.addReferenceToSelect(select, parentAlias, column); - res = 'refAdded'; + res = true; } - if (tableAction == 'loadRequired') { - return 'loadRequired'; - } - } else { - this.requireFkTarget(column); - res = 'loadRequired'; } } } @@ -100,38 +87,40 @@ export class TableGridDisplay extends GridDisplay { 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 }, + const table = this.getFkTarget(column); + if (table) { + 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 }, + }, }, - right: { - exprType: 'column', - columnName: table.primaryKey.columns[0].columnName, - source: { name: table, alias: childAlias }, - }, - }, - ], - }, - ]; + ], + }, + ]; + } } - addHintsToSelect(select: Select): ReferenceActionResult { - let res: ReferenceActionResult = 'noAction'; + addHintsToSelect(select: Select): boolean { + let res = false; for (const column of this.getGridColumns()) { if (column.foreignKey) { - const table = this.cache.tables[column.uniqueName]; + const table = this.getFkTarget(column); if (table && table.columns && table.columns.length > 0) { const hintColumn = table.columns.find((x) => x?.dataType?.toLowerCase()?.includes('char')); if (hintColumn) { @@ -144,12 +133,8 @@ export class TableGridDisplay extends GridDisplay { alias: `hint_${column.uniqueName}`, source: { alias: childAlias }, }); - res = 'refAdded'; + res = true; } - } else { - this.requireFkTarget(column); - this.isLoadedCorrectly = false; - res = 'loadRequired'; } } } @@ -166,51 +151,26 @@ export class TableGridDisplay extends GridDisplay { } getExpandedColumns(column: DisplayColumn) { - const table = this.cache.tables[column.uniqueName]; + const table = this.getFkTarget(column); if (table) { return this.enrichExpandedColumns(this.getDisplayColumns(table, column.uniquePath)); - } else { - // load expanded columns - this.requireFkTarget(column); } return []; } - loadTableIntoCache(key, { pureName, schemaName }) { - if (this.cache.loadingTables.find((x) => x.pureName == pureName && x.schemaName == schemaName)) return; - - this.setCache((cache) => ({ - ...cache, - loadingTables: [...cache.loadingTables, { schemaName, pureName }], - })); - - this.getTableInfo({ schemaName, pureName }).then((table) => { - console.log('Loading table info', schemaName, pureName); - this.setCache((cache) => ({ - ...cache, - loadingTables: cache.loadingTables.filter((x) => x.schemaName != schemaName || x.pureName != pureName), - tables: { - ...cache.tables, - [key]: table, - }, - })); - }); - } - - requireFkTarget(column: DisplayColumn) { + getFkTarget(column: DisplayColumn) { const { uniqueName, foreignKey } = column; const pureName = foreignKey.refTableName; const schemaName = foreignKey.refSchemaName; - this.loadTableIntoCache(uniqueName, { pureName, schemaName }); + return this.findTable({ schemaName, pureName }); } - processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo): ReferenceActionResult { - const action = combineReferenceActions( - this.addJoinsFromExpandedColumns(select, this.columns, 'basetbl', displayedColumnInfo), - this.addHintsToSelect(select) - ); + processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo): boolean { + let res = false; + if (this.addJoinsFromExpandedColumns(select, this.columns, 'basetbl', displayedColumnInfo)) res = true; + if (this.addHintsToSelect(select)) res = true; if (select.from.relations) select.from.relations.reverse(); - return action; + return res; } createSelect() { @@ -246,8 +206,8 @@ export class TableGridDisplay extends GridDisplay { columns: DisplayColumn[], parentAlias: string, displayedColumnInfo: DisplayedColumnInfo - ): ReferenceActionResult { - let res: ReferenceActionResult = 'noAction'; + ): boolean { + let res = false; for (const column of columns) { if (this.config.addedColumns.includes(column.uniqueName)) { select.columns.push({ @@ -260,7 +220,7 @@ export class TableGridDisplay extends GridDisplay { ...column, sourceAlias: parentAlias, }; - res = 'refAdded'; + res = true; } } return res; diff --git a/packages/web/src/datagrid/TableDataGrid.js b/packages/web/src/datagrid/TableDataGrid.js index 84321ece..14438645 100644 --- a/packages/web/src/datagrid/TableDataGrid.js +++ b/packages/web/src/datagrid/TableDataGrid.js @@ -4,7 +4,7 @@ import DataGrid from './DataGrid'; import styled from 'styled-components'; import { TableGridDisplay, createGridConfig, createGridCache } from '@dbgate/datalib'; import { getFilterValueExpression } from '@dbgate/filterparser'; -import { useConnectionInfo, getTableInfo } from '../utility/metadataLoaders'; +import { useConnectionInfo, getTableInfo, useDatabaseInfo } from '../utility/metadataLoaders'; import engines from '@dbgate/engines'; import useSocket from '../utility/SocketProvider'; import { VerticalSplitter } from '../widgets/Splitter'; @@ -50,6 +50,7 @@ export default function TableDataGrid({ const [myLoadedTime, setMyLoadedTime] = React.useState(0); const connection = useConnectionInfo({ conid }); + const dbinfo = useDatabaseInfo({ conid, database }); const [reference, setReference] = React.useState(null); React.useEffect(() => { @@ -66,10 +67,10 @@ export default function TableDataGrid({ setConfig || setMyConfig, cache || myCache, setCache || setMyCache, - (name) => getTableInfo({ conid, database, ...name }) + dbinfo ) : null, - [connection, config || myConfig, cache || myCache, conid, database, schemaName, pureName] + [connection, config || myConfig, cache || myCache, conid, database, schemaName, pureName, dbinfo] ); const handleDatabaseStructureChanged = React.useCallback(() => { diff --git a/packages/web/src/utility/metadataLoaders.js b/packages/web/src/utility/metadataLoaders.js index 0f19e236..25d2353a 100644 --- a/packages/web/src/utility/metadataLoaders.js +++ b/packages/web/src/utility/metadataLoaders.js @@ -3,17 +3,23 @@ import axios from './axios'; import { cacheGet, cacheSet, getCachedPromise } from './cache'; import stableStringify from 'json-stable-stringify'; -const tableInfoLoader = ({ conid, database, schemaName, pureName }) => ({ - url: 'metadata/table-info', - params: { conid, database, schemaName, pureName }, +const databaseInfoLoader = ({ conid, database }) => ({ + url: 'database-connections/structure', + params: { conid, database }, reloadTrigger: `database-structure-changed-${conid}-${database}`, }); -const sqlObjectInfoLoader = ({ objectTypeField, conid, database, schemaName, pureName }) => ({ - url: 'metadata/sql-object-info', - params: { objectTypeField, conid, database, schemaName, pureName }, - reloadTrigger: `database-structure-changed-${conid}-${database}`, -}); +// const tableInfoLoader = ({ conid, database, schemaName, pureName }) => ({ +// url: 'metadata/table-info', +// params: { conid, database, schemaName, pureName }, +// reloadTrigger: `database-structure-changed-${conid}-${database}`, +// }); + +// const sqlObjectInfoLoader = ({ objectTypeField, conid, database, schemaName, pureName }) => ({ +// url: 'metadata/sql-object-info', +// params: { objectTypeField, conid, database, schemaName, pureName }, +// reloadTrigger: `database-structure-changed-${conid}-${database}`, +// }); const connectionInfoLoader = ({ conid }) => ({ url: 'connections/get', @@ -21,11 +27,11 @@ const connectionInfoLoader = ({ conid }) => ({ reloadTrigger: 'connection-list-changed', }); -const sqlObjectListLoader = ({ conid, database }) => ({ - url: 'metadata/list-objects', - params: { conid, database }, - reloadTrigger: `database-structure-changed-${conid}-${database}`, -}); +// const sqlObjectListLoader = ({ conid, database }) => ({ +// url: 'metadata/list-objects', +// params: { conid, database }, +// reloadTrigger: `database-structure-changed-${conid}-${database}`, +// }); const databaseStatusLoader = ({ conid, database }) => ({ url: 'database-connections/status', @@ -86,32 +92,56 @@ function useCore(loader, args) { return res; } +/** @returns {Promise} */ +export function getDatabaseInfo(args) { + return getCore(databaseInfoLoader, args); +} + +/** @returns {import('@dbgate/types').DatabaseInfo} */ +export function useDatabaseInfo(args) { + return useCore(databaseInfoLoader, args); +} + +async function getDbCore(args, objectTypeField = undefined) { + const db = await getDatabaseInfo(args); + return db[objectTypeField || args.objectTypeField].find( + (x) => x.pureName == args.pureName && x.schemaName == args.schemaName + ); +} + +export function useDbCore(args, objectTypeField = undefined) { + const db = useDatabaseInfo(args); + return db[objectTypeField || args.objectTypeField].find( + (x) => x.pureName == args.pureName && x.schemaName == args.schemaName + ); +} + /** @returns {Promise} */ export function getTableInfo(args) { - return getCore(tableInfoLoader, args); + return getDbCore(args, 'tables'); } /** @returns {import('@dbgate/types').TableInfo} */ export function useTableInfo(args) { - return useCore(tableInfoLoader, args); + return useDbCore(args, 'tables'); } /** @returns {Promise} */ export function getViewInfo(args) { - return getCore(sqlObjectInfoLoader, { ...args, objectTypeField: 'views' }); + return getDbCore(args, 'views'); } /** @returns {import('@dbgate/types').ViewInfo} */ export function useViewInfo(args) { - return useCore(sqlObjectInfoLoader, { ...args, objectTypeField: 'views' }); + return useDbCore(args, 'views'); } export function getSqlObjectInfo(args) { - return getCore(sqlObjectInfoLoader, args); + return getDbCore(args); } export function useSqlObjectInfo(args) { - return useCore(sqlObjectInfoLoader, args); + return useDbCore(args); } /** @returns {Promise} */ @@ -124,12 +154,12 @@ export function useConnectionInfo(args) { return useCore(connectionInfoLoader, args); } -export function getSqlObjectList(args) { - return getCore(sqlObjectListLoader, args); -} -export function useSqlObjectList(args) { - return useCore(sqlObjectListLoader, args); -} +// export function getSqlObjectList(args) { +// return getCore(sqlObjectListLoader, args); +// } +// export function useSqlObjectList(args) { +// return useCore(sqlObjectListLoader, args); +// } export function getDatabaseStatus(args) { return getCore(databaseStatusLoader, args); diff --git a/packages/web/src/widgets/DatabaseWidget.js b/packages/web/src/widgets/DatabaseWidget.js index 27166884..586ee9d2 100644 --- a/packages/web/src/widgets/DatabaseWidget.js +++ b/packages/web/src/widgets/DatabaseWidget.js @@ -8,11 +8,12 @@ import { useSetCurrentDatabase, useCurrentDatabase, useOpenedConnections } from import InlineButton from './InlineButton'; import databaseObjectAppObject from '../appobj/databaseObjectAppObject'; import { - useSqlObjectList, + // useSqlObjectList, useDatabaseList, useConnectionList, useServerStatus, useDatabaseStatus, + useDatabaseInfo, } from '../utility/metadataLoaders'; import { SearchBoxWrapper, @@ -82,7 +83,7 @@ function ConnectionList() { } function SqlObjectList({ conid, database }) { - const objects = useSqlObjectList({ conid, database }); + const objects = useDatabaseInfo({ conid, database }); const status = useDatabaseStatus({ conid, database }); const handleRefreshDatabase = () => {