loading full DB structure to client

This commit is contained in:
Jan Prochazka
2020-05-12 21:05:10 +02:00
parent 0515300c73
commit 7750182d1f
7 changed files with 144 additions and 149 deletions

View File

@@ -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', // runCommand_meta: 'post',
// async runCommand({ conid, database, sql }) { // async runCommand({ conid, database, sql }) {
// console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`); // console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`);

View File

@@ -27,8 +27,6 @@ export interface GridConfig extends GridConfigColumns {
} }
export interface GridCache { export interface GridCache {
tables: { [uniqueName: string]: TableInfo };
loadingTables: { schemaName: string; pureName: string }[];
refreshTime: number; refreshTime: number;
} }
@@ -46,8 +44,6 @@ export function createGridConfig(): GridConfig {
export function createGridCache(): GridCache { export function createGridCache(): GridCache {
return { return {
tables: {},
loadingTables: [],
refreshTime: new Date().getTime(), refreshTime: new Date().getTime(),
}; };
} }

View File

@@ -30,13 +30,13 @@ export interface DisplayedColumnInfo {
[uniqueName: string]: DisplayedColumnEx; [uniqueName: string]: DisplayedColumnEx;
} }
export type ReferenceActionResult = 'noAction' | 'loadRequired' | 'refAdded'; // export type ReferenceActionResult = 'noAction' | 'loadRequired' | 'refAdded';
export function combineReferenceActions(a: ReferenceActionResult, b: ReferenceActionResult): ReferenceActionResult { // export function combineReferenceActions(a: ReferenceActionResult, b: ReferenceActionResult): ReferenceActionResult {
if (a == 'loadRequired' || b == 'loadRequired') return 'loadRequired'; // if (a == 'loadRequired' || b == 'loadRequired') return 'loadRequired';
if (a == 'refAdded' || b == 'refAdded') return 'refAdded'; // if (a == 'refAdded' || b == 'refAdded') return 'refAdded';
return 'noAction'; // return 'noAction';
} // }
export type ChangeCacheFunc = (changeFunc: (cache: GridCache) => GridCache) => void; export type ChangeCacheFunc = (changeFunc: (cache: GridCache) => GridCache) => void;
export type ChangeConfigFunc = (changeFunc: (config: GridConfig) => GridConfig) => void; export type ChangeConfigFunc = (changeFunc: (config: GridConfig) => GridConfig) => void;
@@ -269,9 +269,7 @@ export abstract class GridDisplay {
return null; return null;
} }
processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo): ReferenceActionResult { processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo) {}
return 'noAction';
}
createSelectBase(name: NamedObjectInfo, columns: ColumnInfo[]) { createSelectBase(name: NamedObjectInfo, columns: ColumnInfo[]) {
if (!columns) return null; if (!columns) return null;
@@ -297,12 +295,9 @@ export abstract class GridDisplay {
this.columns.map((col) => ({ ...col, sourceAlias: 'basetbl' })), this.columns.map((col) => ({ ...col, sourceAlias: 'basetbl' })),
'uniqueName' 'uniqueName'
); );
const action = this.processReferences(select, displayedColumnInfo); this.processReferences(select, displayedColumnInfo);
this.applyFilterOnSelect(select, displayedColumnInfo); this.applyFilterOnSelect(select, displayedColumnInfo);
this.applySortOnSelect(select, displayedColumnInfo); this.applySortOnSelect(select, displayedColumnInfo);
if (action == 'loadRequired') {
return null;
}
return select; return select;
} }

View File

@@ -1,14 +1,6 @@
import _ from 'lodash'; import _ from 'lodash';
import { import { GridDisplay, ChangeCacheFunc, DisplayColumn, DisplayedColumnInfo, ChangeConfigFunc } from './GridDisplay';
GridDisplay, import { TableInfo, EngineDriver, ViewInfo, ColumnInfo, NamedObjectInfo, DatabaseInfo } from '@dbgate/types';
combineReferenceActions,
ChangeCacheFunc,
DisplayColumn,
ReferenceActionResult,
DisplayedColumnInfo,
ChangeConfigFunc,
} from './GridDisplay';
import { TableInfo, EngineDriver, ViewInfo, ColumnInfo, NamedObjectInfo } from '@dbgate/types';
import { GridConfig, GridCache, createGridCache } from './GridConfig'; import { GridConfig, GridCache, createGridCache } from './GridConfig';
import { Expression, Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree'; import { Expression, Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree';
import { filterName } from './filterName'; import { filterName } from './filterName';
@@ -23,14 +15,12 @@ export class TableGridDisplay extends GridDisplay {
setConfig: ChangeConfigFunc, setConfig: ChangeConfigFunc,
cache: GridCache, cache: GridCache,
setCache: ChangeCacheFunc, setCache: ChangeCacheFunc,
protected getTableInfo: ({ schemaName, pureName }) => Promise<TableInfo> protected dbinfo: DatabaseInfo
) { ) {
super(config, setConfig, cache, setCache, driver); super(config, setConfig, cache, setCache, driver);
const baseTblCacheKey = `basetbl_${tableName.schemaName}_${tableName.pureName}`; this.table = this.findTable(tableName);
this.table = this.cache.tables[baseTblCacheKey];
if (!this.table) { if (!this.table) {
this.loadTableIntoCache(baseTblCacheKey, tableName);
this.isLoadedCorrectly = false; this.isLoadedCorrectly = false;
} else { } else {
if (!this.table.columns || this.table.columns.length == 0) { 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[]) { getDisplayColumns(table: TableInfo, parentPath: string[]) {
return ( return (
table?.columns table?.columns
@@ -62,34 +60,23 @@ export class TableGridDisplay extends GridDisplay {
); );
} }
addJoinsFromExpandedColumns( addJoinsFromExpandedColumns(select: Select, columns: DisplayColumn[], parentAlias: string, columnSources): boolean {
select: Select, let res = false;
columns: DisplayColumn[],
parentAlias: string,
columnSources
): ReferenceActionResult {
let res: ReferenceActionResult = 'noAction';
for (const column of columns) { for (const column of columns) {
if (this.isExpandedColumn(column.uniqueName)) { if (this.isExpandedColumn(column.uniqueName)) {
const table = this.cache.tables[column.uniqueName]; const table = this.getFkTarget(column);
if (table) { if (table) {
const childAlias = `${column.uniqueName}_ref`; const childAlias = `${column.uniqueName}_ref`;
const subcolumns = this.getDisplayColumns(table, column.uniquePath); 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); 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) { addReferenceToSelect(select: Select, parentAlias: string, column: DisplayColumn) {
const childAlias = `${column.uniqueName}_ref`; const childAlias = `${column.uniqueName}_ref`;
if ((select.from.relations || []).find((x) => x.alias == childAlias)) return; if ((select.from.relations || []).find((x) => x.alias == childAlias)) return;
const table = this.cache.tables[column.uniqueName]; const table = this.getFkTarget(column);
select.from.relations = [ if (table) {
...(select.from.relations || []), select.from.relations = [
{ ...(select.from.relations || []),
joinType: 'LEFT JOIN', {
name: table, joinType: 'LEFT JOIN',
alias: childAlias, name: table,
conditions: [ alias: childAlias,
{ conditions: [
conditionType: 'binary', {
operator: '=', conditionType: 'binary',
left: { operator: '=',
exprType: 'column', left: {
columnName: column.columnName, exprType: 'column',
source: { name: column, alias: parentAlias }, 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 { addHintsToSelect(select: Select): boolean {
let res: ReferenceActionResult = 'noAction'; let res = false;
for (const column of this.getGridColumns()) { for (const column of this.getGridColumns()) {
if (column.foreignKey) { if (column.foreignKey) {
const table = this.cache.tables[column.uniqueName]; const table = this.getFkTarget(column);
if (table && table.columns && table.columns.length > 0) { if (table && table.columns && table.columns.length > 0) {
const hintColumn = table.columns.find((x) => x?.dataType?.toLowerCase()?.includes('char')); const hintColumn = table.columns.find((x) => x?.dataType?.toLowerCase()?.includes('char'));
if (hintColumn) { if (hintColumn) {
@@ -144,12 +133,8 @@ export class TableGridDisplay extends GridDisplay {
alias: `hint_${column.uniqueName}`, alias: `hint_${column.uniqueName}`,
source: { alias: childAlias }, 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) { getExpandedColumns(column: DisplayColumn) {
const table = this.cache.tables[column.uniqueName]; const table = this.getFkTarget(column);
if (table) { if (table) {
return this.enrichExpandedColumns(this.getDisplayColumns(table, column.uniquePath)); return this.enrichExpandedColumns(this.getDisplayColumns(table, column.uniquePath));
} else {
// load expanded columns
this.requireFkTarget(column);
} }
return []; return [];
} }
loadTableIntoCache(key, { pureName, schemaName }) { getFkTarget(column: DisplayColumn) {
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) {
const { uniqueName, foreignKey } = column; const { uniqueName, foreignKey } = column;
const pureName = foreignKey.refTableName; const pureName = foreignKey.refTableName;
const schemaName = foreignKey.refSchemaName; const schemaName = foreignKey.refSchemaName;
this.loadTableIntoCache(uniqueName, { pureName, schemaName }); return this.findTable({ schemaName, pureName });
} }
processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo): ReferenceActionResult { processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo): boolean {
const action = combineReferenceActions( let res = false;
this.addJoinsFromExpandedColumns(select, this.columns, 'basetbl', displayedColumnInfo), if (this.addJoinsFromExpandedColumns(select, this.columns, 'basetbl', displayedColumnInfo)) res = true;
this.addHintsToSelect(select) if (this.addHintsToSelect(select)) res = true;
);
if (select.from.relations) select.from.relations.reverse(); if (select.from.relations) select.from.relations.reverse();
return action; return res;
} }
createSelect() { createSelect() {
@@ -246,8 +206,8 @@ export class TableGridDisplay extends GridDisplay {
columns: DisplayColumn[], columns: DisplayColumn[],
parentAlias: string, parentAlias: string,
displayedColumnInfo: DisplayedColumnInfo displayedColumnInfo: DisplayedColumnInfo
): ReferenceActionResult { ): boolean {
let res: ReferenceActionResult = 'noAction'; let res = false;
for (const column of columns) { for (const column of columns) {
if (this.config.addedColumns.includes(column.uniqueName)) { if (this.config.addedColumns.includes(column.uniqueName)) {
select.columns.push({ select.columns.push({
@@ -260,7 +220,7 @@ export class TableGridDisplay extends GridDisplay {
...column, ...column,
sourceAlias: parentAlias, sourceAlias: parentAlias,
}; };
res = 'refAdded'; res = true;
} }
} }
return res; return res;

View File

@@ -4,7 +4,7 @@ import DataGrid from './DataGrid';
import styled from 'styled-components'; import styled from 'styled-components';
import { TableGridDisplay, createGridConfig, createGridCache } from '@dbgate/datalib'; import { TableGridDisplay, createGridConfig, createGridCache } from '@dbgate/datalib';
import { getFilterValueExpression } from '@dbgate/filterparser'; import { getFilterValueExpression } from '@dbgate/filterparser';
import { useConnectionInfo, getTableInfo } from '../utility/metadataLoaders'; import { useConnectionInfo, getTableInfo, useDatabaseInfo } from '../utility/metadataLoaders';
import engines from '@dbgate/engines'; import engines from '@dbgate/engines';
import useSocket from '../utility/SocketProvider'; import useSocket from '../utility/SocketProvider';
import { VerticalSplitter } from '../widgets/Splitter'; import { VerticalSplitter } from '../widgets/Splitter';
@@ -50,6 +50,7 @@ export default function TableDataGrid({
const [myLoadedTime, setMyLoadedTime] = React.useState(0); const [myLoadedTime, setMyLoadedTime] = React.useState(0);
const connection = useConnectionInfo({ conid }); const connection = useConnectionInfo({ conid });
const dbinfo = useDatabaseInfo({ conid, database });
const [reference, setReference] = React.useState(null); const [reference, setReference] = React.useState(null);
React.useEffect(() => { React.useEffect(() => {
@@ -66,10 +67,10 @@ export default function TableDataGrid({
setConfig || setMyConfig, setConfig || setMyConfig,
cache || myCache, cache || myCache,
setCache || setMyCache, setCache || setMyCache,
(name) => getTableInfo({ conid, database, ...name }) dbinfo
) )
: null, : null,
[connection, config || myConfig, cache || myCache, conid, database, schemaName, pureName] [connection, config || myConfig, cache || myCache, conid, database, schemaName, pureName, dbinfo]
); );
const handleDatabaseStructureChanged = React.useCallback(() => { const handleDatabaseStructureChanged = React.useCallback(() => {

View File

@@ -3,17 +3,23 @@ import axios from './axios';
import { cacheGet, cacheSet, getCachedPromise } from './cache'; import { cacheGet, cacheSet, getCachedPromise } from './cache';
import stableStringify from 'json-stable-stringify'; import stableStringify from 'json-stable-stringify';
const tableInfoLoader = ({ conid, database, schemaName, pureName }) => ({ const databaseInfoLoader = ({ conid, database }) => ({
url: 'metadata/table-info', url: 'database-connections/structure',
params: { conid, database, schemaName, pureName }, params: { conid, database },
reloadTrigger: `database-structure-changed-${conid}-${database}`, reloadTrigger: `database-structure-changed-${conid}-${database}`,
}); });
const sqlObjectInfoLoader = ({ objectTypeField, conid, database, schemaName, pureName }) => ({ // const tableInfoLoader = ({ conid, database, schemaName, pureName }) => ({
url: 'metadata/sql-object-info', // url: 'metadata/table-info',
params: { objectTypeField, conid, database, schemaName, pureName }, // params: { conid, database, schemaName, pureName },
reloadTrigger: `database-structure-changed-${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 connectionInfoLoader = ({ conid }) => ({ const connectionInfoLoader = ({ conid }) => ({
url: 'connections/get', url: 'connections/get',
@@ -21,11 +27,11 @@ const connectionInfoLoader = ({ conid }) => ({
reloadTrigger: 'connection-list-changed', reloadTrigger: 'connection-list-changed',
}); });
const sqlObjectListLoader = ({ conid, database }) => ({ // const sqlObjectListLoader = ({ conid, database }) => ({
url: 'metadata/list-objects', // url: 'metadata/list-objects',
params: { conid, database }, // params: { conid, database },
reloadTrigger: `database-structure-changed-${conid}-${database}`, // reloadTrigger: `database-structure-changed-${conid}-${database}`,
}); // });
const databaseStatusLoader = ({ conid, database }) => ({ const databaseStatusLoader = ({ conid, database }) => ({
url: 'database-connections/status', url: 'database-connections/status',
@@ -86,32 +92,56 @@ function useCore(loader, args) {
return res; return res;
} }
/** @returns {Promise<import('@dbgate/types').DatabaseInfo>} */
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<import('@dbgate/types').TableInfo>} */ /** @returns {Promise<import('@dbgate/types').TableInfo>} */
export function getTableInfo(args) { export function getTableInfo(args) {
return getCore(tableInfoLoader, args); return getDbCore(args, 'tables');
} }
/** @returns {import('@dbgate/types').TableInfo} */ /** @returns {import('@dbgate/types').TableInfo} */
export function useTableInfo(args) { export function useTableInfo(args) {
return useCore(tableInfoLoader, args); return useDbCore(args, 'tables');
} }
/** @returns {Promise<import('@dbgate/types').ViewInfo>} */ /** @returns {Promise<import('@dbgate/types').ViewInfo>} */
export function getViewInfo(args) { export function getViewInfo(args) {
return getCore(sqlObjectInfoLoader, { ...args, objectTypeField: 'views' }); return getDbCore(args, 'views');
} }
/** @returns {import('@dbgate/types').ViewInfo} */ /** @returns {import('@dbgate/types').ViewInfo} */
export function useViewInfo(args) { export function useViewInfo(args) {
return useCore(sqlObjectInfoLoader, { ...args, objectTypeField: 'views' }); return useDbCore(args, 'views');
} }
export function getSqlObjectInfo(args) { export function getSqlObjectInfo(args) {
return getCore(sqlObjectInfoLoader, args); return getDbCore(args);
} }
export function useSqlObjectInfo(args) { export function useSqlObjectInfo(args) {
return useCore(sqlObjectInfoLoader, args); return useDbCore(args);
} }
/** @returns {Promise<import('@dbgate/types').StoredConnection>} */ /** @returns {Promise<import('@dbgate/types').StoredConnection>} */
@@ -124,12 +154,12 @@ export function useConnectionInfo(args) {
return useCore(connectionInfoLoader, args); return useCore(connectionInfoLoader, args);
} }
export function getSqlObjectList(args) { // export function getSqlObjectList(args) {
return getCore(sqlObjectListLoader, args); // return getCore(sqlObjectListLoader, args);
} // }
export function useSqlObjectList(args) { // export function useSqlObjectList(args) {
return useCore(sqlObjectListLoader, args); // return useCore(sqlObjectListLoader, args);
} // }
export function getDatabaseStatus(args) { export function getDatabaseStatus(args) {
return getCore(databaseStatusLoader, args); return getCore(databaseStatusLoader, args);

View File

@@ -8,11 +8,12 @@ import { useSetCurrentDatabase, useCurrentDatabase, useOpenedConnections } from
import InlineButton from './InlineButton'; import InlineButton from './InlineButton';
import databaseObjectAppObject from '../appobj/databaseObjectAppObject'; import databaseObjectAppObject from '../appobj/databaseObjectAppObject';
import { import {
useSqlObjectList, // useSqlObjectList,
useDatabaseList, useDatabaseList,
useConnectionList, useConnectionList,
useServerStatus, useServerStatus,
useDatabaseStatus, useDatabaseStatus,
useDatabaseInfo,
} from '../utility/metadataLoaders'; } from '../utility/metadataLoaders';
import { import {
SearchBoxWrapper, SearchBoxWrapper,
@@ -82,7 +83,7 @@ function ConnectionList() {
} }
function SqlObjectList({ conid, database }) { function SqlObjectList({ conid, database }) {
const objects = useSqlObjectList({ conid, database }); const objects = useDatabaseInfo({ conid, database });
const status = useDatabaseStatus({ conid, database }); const status = useDatabaseStatus({ conid, database });
const handleRefreshDatabase = () => { const handleRefreshDatabase = () => {