diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index 926adb194..fa5e5a233 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -16,6 +16,15 @@ export interface DisplayColumn { isPrimaryKey: boolean; foreignKey: ForeignKeyInfo; 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 { @@ -81,31 +90,35 @@ export abstract class GridDisplay { const res = []; for (const item of list) { 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; } - getExpandedColumns(column: DisplayColumn, uniqueName: string) { - const table = this.cache.tables[uniqueName]; + getExpandedColumns(column: DisplayColumn) { + const table = this.cache.tables[column.uniqueName]; if (table) { return this.enrichExpandedColumns(this.getDisplayColumns(table, column.uniquePath)); } else { // load expanded columns - const { foreignKey } = column; - this.getTableInfo({ schemaName: foreignKey.refSchemaName, pureName: foreignKey.refTableName }).then(table => { - this.setCache({ - ...this.cache, - tables: { - ...this.cache.tables, - [uniqueName]: table, - }, - }); - }); + this.requireFkTarget(column); } 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) { return column.uniquePath.length == 1 ? !this.config.hiddenColumns.includes(column.uniqueName) @@ -130,8 +143,8 @@ export abstract class GridDisplay { }; } - addAddedColumnsToSelect(select: Select, columns: DisplayColumn[], parentAlias: string) { - let res = false; + addAddedColumnsToSelect(select: Select, columns: DisplayColumn[], parentAlias: string): ReferenceActionResult { + let res: ReferenceActionResult = 'noAction'; for (const column of columns) { if (this.config.addedColumns.includes(column.uniqueName)) { select.columns.push({ @@ -139,52 +152,35 @@ export abstract class GridDisplay { columnName: column.columnName, source: { name: column, alias: parentAlias }, }); - res = true; + res = 'refAdded'; } } return res; } - addJoinsFromExpandedColumns(select: Select, columns: DisplayColumn[], parentAlias: string) { - let res = false; + addJoinsFromExpandedColumns(select: Select, columns: DisplayColumn[], parentAlias: string): 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 usedTable = - this.addJoinsFromExpandedColumns(select, subcolumns, childAlias) || - this.addAddedColumnsToSelect(select, subcolumns, childAlias); + const tableAction = combineReferenceActions( + this.addJoinsFromExpandedColumns(select, subcolumns, childAlias), + this.addAddedColumnsToSelect(select, subcolumns, childAlias) + ); - if (usedTable) { - 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 }, - }, - }, - ], - }, - ]; - - res = true; + if (tableAction == 'refAdded') { + this.addReferenceToSelect(select, parentAlias, column); + res = 'refAdded'; } + 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.) } + 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[]) { return table?.columns ?.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) { diff --git a/packages/datalib/src/TableGridDisplay.ts b/packages/datalib/src/TableGridDisplay.ts index 298c807f7..c9a1b3278 100644 --- a/packages/datalib/src/TableGridDisplay.ts +++ b/packages/datalib/src/TableGridDisplay.ts @@ -1,4 +1,4 @@ -import { GridDisplay } from './GridDisplay'; +import { GridDisplay, combineReferenceActions } from './GridDisplay'; import { Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree'; import { TableInfo, EngineDriver } from '@dbgate/types'; 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; } 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); diff --git a/packages/web/src/datagrid/ColumnLabel.js b/packages/web/src/datagrid/ColumnLabel.js index baf6d81ee..5a0bc9933 100644 --- a/packages/web/src/datagrid/ColumnLabel.js +++ b/packages/web/src/datagrid/ColumnLabel.js @@ -2,7 +2,7 @@ import React from 'react'; import styled from 'styled-components'; -import { SequenceIcon } from '../icons'; +import { SequenceIcon, ForeignKeyIcon } from '../icons'; const Label = styled.span` font-weight: ${props => (props.notNull ? 'bold' : 'normal')}; @@ -12,6 +12,7 @@ const Label = styled.span` export default function ColumnLabel(column) { let Icon = null; if (column.autoIncrement) Icon = SequenceIcon; + if (column.foreignKey) Icon = ForeignKeyIcon; return (