mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-17 22:36:01 +00:00
browse view data
This commit is contained in:
@@ -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}`);
|
||||
|
||||
47
packages/api/src/controllers/metadata.js
Normal file
47
packages/api/src/controllers/metadata.js
Normal file
@@ -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;
|
||||
},
|
||||
};
|
||||
@@ -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),
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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<TableInfo>,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TableInfo>
|
||||
protected getTableInfo: ({ schemaName, pureName }) => Promise<TableInfo>
|
||||
) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
50
packages/datalib/src/ViewGridDisplay.ts
Normal file
50
packages/datalib/src/ViewGridDisplay.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
18
packages/engines/mssql/sql/viewColumns.js
Normal file
18
packages/engines/mssql/sql/viewColumns.js
Normal file
@@ -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
|
||||
`;
|
||||
4
packages/types/dbinfo.d.ts
vendored
4
packages/types/dbinfo.d.ts
vendored
@@ -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 {}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
// <>
|
||||
// <DropDownMenuItem onClick={handleOpenData}>Open data</DropDownMenuItem>
|
||||
// <DropDownMenuItem onClick={handleOpenStructure}>Open structure</DropDownMenuItem>
|
||||
// <DropDownMenuItem onClick={handleOpenCreateScript}>Create SQL</DropDownMenuItem>
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
function Menu({ data, makeAppObj, setOpenedTabs }) {
|
||||
const handleOpenData = () => {
|
||||
openViewDetail(setOpenedTabs, 'TableDataTab', data);
|
||||
};
|
||||
const handleOpenCreateScript = () => {
|
||||
openViewDetail(setOpenedTabs, 'TableCreateScriptTab', data);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<DropDownMenuItem onClick={handleOpenData}>Open data</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={handleOpenCreateScript}>Create SQL</DropDownMenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
50
packages/web/src/tabs/ViewDataTab.js
Normal file
50
packages/web/src/tabs/ViewDataTab.js
Normal file
@@ -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 (
|
||||
<DataGrid
|
||||
// key={`${conid}, ${database}, ${schemaName}, ${pureName}`}
|
||||
conid={conid}
|
||||
database={database}
|
||||
display={display}
|
||||
tabVisible={tabVisible}
|
||||
changeSetState={changeSetState}
|
||||
dispatchChangeSet={dispatchChangeSet}
|
||||
toolbarPortalRef={toolbarPortalRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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} */
|
||||
|
||||
@@ -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}`,
|
||||
});
|
||||
|
||||
11
packages/web/src/utility/useViewInfo.js
Normal file
11
packages/web/src/utility/useViewInfo.js
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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}`,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user