mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-01 01:33:59 +00:00
FK hints
This commit is contained in:
@@ -16,6 +16,15 @@ export interface DisplayColumn {
|
|||||||
isPrimaryKey: boolean;
|
isPrimaryKey: boolean;
|
||||||
foreignKey: ForeignKeyInfo;
|
foreignKey: ForeignKeyInfo;
|
||||||
isChecked?: boolean;
|
isChecked?: boolean;
|
||||||
|
hintColumnName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 abstract class GridDisplay {
|
export abstract class GridDisplay {
|
||||||
@@ -81,31 +90,35 @@ export abstract class GridDisplay {
|
|||||||
const res = [];
|
const res = [];
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
res.push(item);
|
res.push(item);
|
||||||
if (this.isExpandedColumn(item.uniqueName)) res.push(...this.getExpandedColumns(item, item.uniqueName));
|
if (this.isExpandedColumn(item.uniqueName)) res.push(...this.getExpandedColumns(item));
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExpandedColumns(column: DisplayColumn, uniqueName: string) {
|
getExpandedColumns(column: DisplayColumn) {
|
||||||
const table = this.cache.tables[uniqueName];
|
const table = this.cache.tables[column.uniqueName];
|
||||||
if (table) {
|
if (table) {
|
||||||
return this.enrichExpandedColumns(this.getDisplayColumns(table, column.uniquePath));
|
return this.enrichExpandedColumns(this.getDisplayColumns(table, column.uniquePath));
|
||||||
} else {
|
} else {
|
||||||
// load expanded columns
|
// load expanded columns
|
||||||
const { foreignKey } = column;
|
this.requireFkTarget(column);
|
||||||
this.getTableInfo({ schemaName: foreignKey.refSchemaName, pureName: foreignKey.refTableName }).then(table => {
|
|
||||||
this.setCache({
|
|
||||||
...this.cache,
|
|
||||||
tables: {
|
|
||||||
...this.cache.tables,
|
|
||||||
[uniqueName]: table,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requireFkTarget(column: DisplayColumn) {
|
||||||
|
const { uniqueName, foreignKey } = column;
|
||||||
|
this.getTableInfo({ schemaName: foreignKey.refSchemaName, pureName: foreignKey.refTableName }).then(table => {
|
||||||
|
this.setCache({
|
||||||
|
...this.cache,
|
||||||
|
tables: {
|
||||||
|
...this.cache.tables,
|
||||||
|
[uniqueName]: table,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
isColumnChecked(column: DisplayColumn) {
|
isColumnChecked(column: DisplayColumn) {
|
||||||
return column.uniquePath.length == 1
|
return column.uniquePath.length == 1
|
||||||
? !this.config.hiddenColumns.includes(column.uniqueName)
|
? !this.config.hiddenColumns.includes(column.uniqueName)
|
||||||
@@ -130,8 +143,8 @@ export abstract class GridDisplay {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
addAddedColumnsToSelect(select: Select, columns: DisplayColumn[], parentAlias: string) {
|
addAddedColumnsToSelect(select: Select, columns: DisplayColumn[], parentAlias: string): ReferenceActionResult {
|
||||||
let res = false;
|
let res: ReferenceActionResult = 'noAction';
|
||||||
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({
|
||||||
@@ -139,52 +152,35 @@ export abstract class GridDisplay {
|
|||||||
columnName: column.columnName,
|
columnName: column.columnName,
|
||||||
source: { name: column, alias: parentAlias },
|
source: { name: column, alias: parentAlias },
|
||||||
});
|
});
|
||||||
res = true;
|
res = 'refAdded';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
addJoinsFromExpandedColumns(select: Select, columns: DisplayColumn[], parentAlias: string) {
|
addJoinsFromExpandedColumns(select: Select, columns: DisplayColumn[], parentAlias: string): ReferenceActionResult {
|
||||||
let res = false;
|
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.cache.tables[column.uniqueName];
|
||||||
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 usedTable =
|
const tableAction = combineReferenceActions(
|
||||||
this.addJoinsFromExpandedColumns(select, subcolumns, childAlias) ||
|
this.addJoinsFromExpandedColumns(select, subcolumns, childAlias),
|
||||||
this.addAddedColumnsToSelect(select, subcolumns, childAlias);
|
this.addAddedColumnsToSelect(select, subcolumns, childAlias)
|
||||||
|
);
|
||||||
|
|
||||||
if (usedTable) {
|
if (tableAction == 'refAdded') {
|
||||||
select.from.relations = [
|
this.addReferenceToSelect(select, parentAlias, column);
|
||||||
...(select.from.relations || []),
|
res = 'refAdded';
|
||||||
{
|
|
||||||
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 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
res = true;
|
|
||||||
}
|
}
|
||||||
|
if (tableAction == 'loadRequired') {
|
||||||
|
return 'loadRequired';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.requireFkTarget(column);
|
||||||
|
res = 'loadRequired';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,10 +188,72 @@ export abstract class GridDisplay {
|
|||||||
// const addedColumns = this.getGridColumns().filter(x=>x.)
|
// 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.columnName}`,
|
||||||
|
source: { alias: childAlias },
|
||||||
|
});
|
||||||
|
res = 'refAdded';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.requireFkTarget(column);
|
||||||
|
res = 'loadRequired';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
getDisplayColumns(table: TableInfo, parentPath: string[]) {
|
getDisplayColumns(table: TableInfo, parentPath: string[]) {
|
||||||
return table?.columns
|
return table?.columns
|
||||||
?.map(col => this.getDisplayColumn(table, col, parentPath))
|
?.map(col => this.getDisplayColumn(table, col, parentPath))
|
||||||
?.map(col => ({ ...col, isChecked: this.isColumnChecked(col) }));
|
?.map(col => ({
|
||||||
|
...col,
|
||||||
|
isChecked: this.isColumnChecked(col),
|
||||||
|
hintColumnName: col.foreignKey ? `hint_${col.columnName}` : null,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getColumns(columnFilter) {
|
getColumns(columnFilter) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { GridDisplay } from './GridDisplay';
|
import { GridDisplay, combineReferenceActions } from './GridDisplay';
|
||||||
import { Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree';
|
import { Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree';
|
||||||
import { TableInfo, EngineDriver } from '@dbgate/types';
|
import { TableInfo, EngineDriver } from '@dbgate/types';
|
||||||
import { GridConfig, GridCache } from './GridConfig';
|
import { GridConfig, GridCache } from './GridConfig';
|
||||||
@@ -36,12 +36,19 @@ export class TableGridDisplay extends GridDisplay {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
this.addJoinsFromExpandedColumns(select, this.columns, 'basetbl');
|
const action = combineReferenceActions(
|
||||||
|
this.addJoinsFromExpandedColumns(select, this.columns, 'basetbl'),
|
||||||
|
this.addHintsToSelect(select)
|
||||||
|
);
|
||||||
|
if (action == 'loadRequired') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return select;
|
return select;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPageQuery(offset: number, count: number) {
|
getPageQuery(offset: number, count: number) {
|
||||||
const select = this.createSelect();
|
const select = this.createSelect();
|
||||||
|
if (!select) return null;
|
||||||
if (this.driver.dialect.rangeSelect) select.range = { offset: offset, limit: count };
|
if (this.driver.dialect.rangeSelect) select.range = { offset: offset, limit: count };
|
||||||
else if (this.driver.dialect.limitSelect) select.topRecords = count;
|
else if (this.driver.dialect.limitSelect) select.topRecords = count;
|
||||||
const sql = treeToSql(this.driver, select, dumpSqlSelect);
|
const sql = treeToSql(this.driver, select, dumpSqlSelect);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { SequenceIcon } from '../icons';
|
import { SequenceIcon, ForeignKeyIcon } from '../icons';
|
||||||
|
|
||||||
const Label = styled.span`
|
const Label = styled.span`
|
||||||
font-weight: ${props => (props.notNull ? 'bold' : 'normal')};
|
font-weight: ${props => (props.notNull ? 'bold' : 'normal')};
|
||||||
@@ -12,6 +12,7 @@ const Label = styled.span`
|
|||||||
export default function ColumnLabel(column) {
|
export default function ColumnLabel(column) {
|
||||||
let Icon = null;
|
let Icon = null;
|
||||||
if (column.autoIncrement) Icon = SequenceIcon;
|
if (column.autoIncrement) Icon = SequenceIcon;
|
||||||
|
if (column.foreignKey) Icon = ForeignKeyIcon;
|
||||||
return (
|
return (
|
||||||
<Label {...column}>
|
<Label {...column}>
|
||||||
{Icon ? <Icon /> : null} {column.headerText || column.columnName}
|
{Icon ? <Icon /> : null} {column.headerText || column.columnName}
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ const TableBodyCell = styled.td`
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
const HintSpan = styled.span`
|
||||||
|
color: gray;
|
||||||
|
margin-left: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
/** @param props {import('./types').DataGridProps} */
|
/** @param props {import('./types').DataGridProps} */
|
||||||
export default function DataGridCore(props) {
|
export default function DataGridCore(props) {
|
||||||
@@ -163,7 +167,8 @@ export default function DataGridCore(props) {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!isLoadedAll && firstVisibleRowScrollIndex + visibleRowCountUpperBound >= loadedRows.length) {
|
if (!isLoadedAll && firstVisibleRowScrollIndex + visibleRowCountUpperBound >= loadedRows.length) {
|
||||||
loadNextData();
|
const sql = display.getPageQuery(0, 1);
|
||||||
|
if (sql) loadNextData();
|
||||||
}
|
}
|
||||||
if (display.cache.refreshTime > loadedTime) {
|
if (display.cache.refreshTime > loadedTime) {
|
||||||
reload();
|
reload();
|
||||||
@@ -314,6 +319,7 @@ export default function DataGridCore(props) {
|
|||||||
style={{ width: col.widthPx, minWidth: col.widthPx, maxWidth: col.widthPx }}
|
style={{ width: col.widthPx, minWidth: col.widthPx, maxWidth: col.widthPx }}
|
||||||
>
|
>
|
||||||
{row[col.columnName]}
|
{row[col.columnName]}
|
||||||
|
{col.hintColumnName && <HintSpan>{row[col.hintColumnName]}</HintSpan>}
|
||||||
</TableBodyCell>
|
</TableBodyCell>
|
||||||
))}
|
))}
|
||||||
</TableBodyRow>
|
</TableBodyRow>
|
||||||
|
|||||||
Reference in New Issue
Block a user