dbgate/packages/datalib/src/PerspectiveTreeNode.ts
2024-08-28 15:49:32 +02:00

1723 lines
49 KiB
TypeScript

import type {
CollectionInfo,
ColumnInfo,
DatabaseInfo,
FilterBehaviour,
ForeignKeyInfo,
NamedObjectInfo,
RangeDefinition,
TableInfo,
ViewInfo,
} from 'dbgate-types';
import {
detectSqlFilterBehaviour,
equalFullName,
isCollectionInfo,
isTableInfo,
isViewInfo,
mongoFilterBehaviour,
stringFilterBehaviour,
} from 'dbgate-tools';
import {
ChangePerspectiveConfigFunc,
createPerspectiveNodeConfig,
MultipleDatabaseInfo,
PerspectiveConfig,
PerspectiveCustomJoinConfig,
PerspectiveDatabaseConfig,
PerspectiveDatabaseEngineType,
PerspectiveFilterColumnInfo,
PerspectiveNodeConfig,
PerspectiveReferenceConfig,
} from './PerspectiveConfig';
import _isEqual from 'lodash/isEqual';
import _isArray from 'lodash/isArray';
import _cloneDeep from 'lodash/cloneDeep';
import _compact from 'lodash/compact';
import _uniq from 'lodash/uniq';
import _flatten from 'lodash/flatten';
import _uniqBy from 'lodash/uniqBy';
import _sortBy from 'lodash/sortBy';
import _cloneDeepWith from 'lodash/cloneDeepWith';
import _findIndex from 'lodash/findIndex';
import { PerspectiveDataLoadProps, PerspectiveDataProvider } from './PerspectiveDataProvider';
import stableStringify from 'json-stable-stringify';
import { parseFilter } from 'dbgate-filterparser';
import { CompoudCondition, Condition, Expression, Select } from 'dbgate-sqltree';
// import { getPerspectiveDefaultColumns } from './getPerspectiveDefaultColumns';
import uuidv1 from 'uuid/v1';
import { PerspectiveDataPatternColumn } from './PerspectiveDataPattern';
import {
getPerspectiveMostNestedChildColumnName,
getPerspectiveParentColumnName,
perspectiveValueMatcher,
} from './perspectiveTools';
export interface PerspectiveDataLoadPropsWithNode {
props: PerspectiveDataLoadProps;
node: PerspectiveTreeNode;
}
// export function groupPerspectiveLoadProps(
// ...list: PerspectiveDataLoadPropsWithNode[]
// ): PerspectiveDataLoadPropsWithNode[] {
// const res: PerspectiveDataLoadPropsWithNode[] = [];
// for (const item of list) {
// const existing = res.find(
// x =>
// x.node == item.node &&
// x.props.schemaName == item.props.schemaName &&
// x.props.pureName == item.props.pureName &&
// _isEqual(x.props.bindingColumns, item.props.bindingColumns)
// );
// if (existing) {
// existing.props.bindingValues.push(...item.props.bindingValues);
// } else {
// res.push(_cloneDeep(item));
// }
// }
// return res;
// }
export abstract class PerspectiveTreeNode {
constructor(
public dbs: MultipleDatabaseInfo,
public config: PerspectiveConfig,
public setConfig: ChangePerspectiveConfigFunc,
public parentNode: PerspectiveTreeNode,
public dataProvider: PerspectiveDataProvider,
public defaultDatabaseConfig: PerspectiveDatabaseConfig,
public designerId: string
) {
this.nodeConfig = config.nodes.find(x => x.designerId == designerId);
this.parentNodeConfig = parentNode?.nodeConfig;
}
readonly nodeConfig: PerspectiveNodeConfig;
parentNodeConfig: PerspectiveNodeConfig;
// defaultChecked: boolean;
abstract get title();
abstract get codeName();
abstract get isExpandable();
childNodesCache: PerspectiveTreeNode[] = null;
get childNodes(): PerspectiveTreeNode[] {
if (!this.childNodesCache) {
this.childNodesCache = this.generateChildNodes();
}
return this.childNodesCache;
}
abstract generateChildNodes(): PerspectiveTreeNode[];
abstract get icon(): string;
get fieldName() {
return this.codeName;
}
get headerTableAttributes() {
return null;
}
get dataField() {
return this.codeName;
}
get tableCode() {
return null;
}
get namedObject(): NamedObjectInfo {
return null;
}
get tableNodeOrParent(): PerspectiveTableNode {
if (this instanceof PerspectiveTableNode) {
return this;
}
if (this.parentNode == null) {
return null;
}
return this.parentNode.tableNodeOrParent;
}
get engineType(): PerspectiveDatabaseEngineType {
return null;
}
get databaseConfig(): PerspectiveDatabaseConfig {
const res = { ...this.defaultDatabaseConfig };
if (this.nodeConfig?.conid) res.conid = this.nodeConfig?.conid;
if (this.nodeConfig?.database) res.database = this.nodeConfig?.database;
return res;
}
abstract getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps;
get isRoot() {
return this.parentNode == null;
}
get rootNode(): PerspectiveTreeNode {
if (this.isRoot) return this;
return this.parentNode?.rootNode;
}
get isSortable() {
return false;
}
get generatesHiearchicGridColumn() {
return this.isExpandable && this.isCheckedNode;
}
get generatesDataGridColumn() {
return this.isCheckedColumn;
}
get validParentDesignerId() {
if (this.designerId) return this.designerId;
return this.parentNode?.validParentDesignerId;
}
get preloadedLevelData() {
return false;
}
get findByDesignerIdWithoutDesignerId() {
return false;
}
matchChildRow(parentRow: any, childRow: any): boolean {
return true;
}
hasTableCode(code: string) {
return code == this.tableCode || this.parentNode?.hasTableCode(code);
}
// get uniqueName() {
// if (this.parentNode) return `${this.parentNode.uniqueName}::${this.codeName}`;
// return this.codeName;
// }
get level() {
if (this.parentNode) return this.parentNode.level + 1;
return 0;
}
get isExpanded() {
return this.parentNodeConfig?.expandedColumns?.includes(this.codeName);
}
get isCheckedColumn() {
if (this.parentNodeConfig?.checkedColumns?.includes(this.codeName)) return true;
return false;
}
get isChecked() {
return this.isCheckedColumn;
}
get isCheckedNode() {
return !!this.designerId && !!this.config.nodes?.find(x => x.designerId == this.designerId)?.isNodeChecked;
}
get isSecondaryChecked() {
return false;
}
get secondaryCheckable() {
return false;
}
get columnTitle() {
return this.title;
}
get filterBehaviour(): FilterBehaviour {
return stringFilterBehaviour;
}
get columnName() {
return null;
}
get customJoinConfig(): PerspectiveCustomJoinConfig {
return null;
}
get db(): DatabaseInfo {
return this.dbs?.[this.databaseConfig.conid]?.[this.databaseConfig.database];
}
get isCircular() {
return false;
}
hasDesignerIdInIncestors(designerId: string): boolean {
if (designerId == this.designerId) return true;
return this.parentNode?.hasDesignerIdInIncestors(designerId) || false;
}
getChildMatchColumns() {
return [];
}
getParentMatchColumns() {
return [];
}
parseFilterCondition(source = null) {
return null;
}
get hasUncheckedNodeInPath() {
if (!this.parentNode) return false;
if (this.designerId && !this.isCheckedNode) return true;
return this.parentNode.hasUncheckedNodeInPath;
}
get childDataColumn() {
if (this.isCheckedColumn) {
return this.codeName;
}
return null;
}
toggleExpanded(value?: boolean) {
this.includeInNodeSet('expandedColumns', value == null ? !this.isExpanded : value);
}
toggleChecked(value?: boolean) {
this.includeInNodeSet('checkedColumns', value == null ? !this.isChecked : value);
}
toggleCheckedNode(value?: boolean) {
this.setConfig(cfg => {
const oldCheckedValue = cfg.nodes.find(x => x.designerId == this.designerId)?.isNodeChecked;
const [cfgChanged, nodeCfg] = this.ensureNodeConfig(cfg);
const res = {
...cfgChanged,
nodes: cfgChanged.nodes.map(node =>
node.designerId == (this.designerId || nodeCfg.designerId)
? {
...node,
isNodeChecked: value == null ? !oldCheckedValue : value,
}
: node
),
};
return res;
});
}
toggleSecondaryChecked(value?: boolean) {}
createReferenceConfigColumns(): PerspectiveReferenceConfig['columns'] {
return null;
}
ensureNodeConfig(cfg: PerspectiveConfig): [PerspectiveConfig, PerspectiveNodeConfig] {
let node = cfg.nodes.find(x => x.designerId == this.designerId);
if (!node) {
const nodeConfig: PerspectiveNodeConfig = {
...createPerspectiveNodeConfig(this.namedObject),
isAutoGenerated: true,
conid: this.parentNodeConfig?.conid,
database: this.parentNodeConfig?.database,
};
const refConfig: PerspectiveReferenceConfig = {
designerId: uuidv1(),
sourceId: this.parentNode.designerId,
targetId: nodeConfig.designerId,
isAutoGenerated: true,
columns: this.createReferenceConfigColumns(),
};
return [
{
...cfg,
nodes: [...cfg.nodes, nodeConfig],
references: [...cfg.references, refConfig],
},
nodeConfig,
];
}
return [cfg, node];
}
includeInNodeSet(field: 'expandedColumns' | 'uncheckedColumns' | 'checkedColumns', isIncluded: boolean) {
this.setConfig(cfg => {
const changedFields = n => ({
...n,
[field]: isIncluded ? [...(n[field] || []), this.codeName] : (n[field] || []).filter(x => x != this.codeName),
});
const [cfgChanged, nodeCfg] = this.parentNode?.tableNodeOrParent?.ensureNodeConfig(cfg);
const res = {
...cfgChanged,
nodes: cfgChanged.nodes.map(n =>
n.designerId == (this.parentNode?.tableNodeOrParent?.designerId || nodeCfg?.designerId) ? changedFields(n) : n
),
};
return res;
});
}
getFilter() {
return this.parentNodeConfig?.filters?.[this.codeName];
}
getDataLoadColumns() {
return _compact(
_uniq([
...this.childNodes.map(x => x.childDataColumn),
..._flatten(this.childNodes.filter(x => x.isExpandable && x.isChecked).map(x => x.getChildMatchColumns())),
...this.getParentMatchColumns(),
...this.childNodes
.filter(x => x instanceof PerspectivePatternColumnNode)
.filter(x => this.nodeConfig?.checkedColumns?.find(y => y.startsWith(x.codeName + '::')))
.map(x => x.columnName),
])
);
}
getMutliColumnCondition(source): Condition {
if (!this.nodeConfig?.multiColumnFilter) return null;
const base = this.getBaseTableFromThis() as TableInfo | ViewInfo | CollectionInfo;
if (!base) return null;
const isDocDb = isCollectionInfo(base);
if (isDocDb) {
return this.getMutliColumnNoSqlCondition();
} else {
return this.getMutliColumnSqlCondition(source);
}
}
getMutliColumnSqlCondition(source): Condition {
if (!this.nodeConfig?.multiColumnFilter) return null;
const base = this.getBaseTableFromThis() as TableInfo | ViewInfo;
if (!base) return null;
try {
const condition = parseFilter(this.nodeConfig?.multiColumnFilter, stringFilterBehaviour);
if (condition) {
const orCondition: CompoudCondition = {
conditionType: 'or',
conditions: [],
};
for (const column of base.columns || []) {
orCondition.conditions.push(
_cloneDeepWith(condition, (expr: Expression) => {
if (expr.exprType == 'placeholder') {
return {
exprType: 'column',
alias: source,
columnName: column.columnName,
};
}
})
);
}
if (orCondition.conditions.length > 0) {
return orCondition;
}
}
} catch (err) {
console.warn(err.message);
}
return null;
}
getMutliColumnNoSqlCondition(): Condition {
if (!this.nodeConfig?.multiColumnFilter) return null;
const pattern = this.dataProvider?.dataPatterns?.[this.designerId];
if (!pattern) return null;
const condition = parseFilter(this.nodeConfig?.multiColumnFilter, mongoFilterBehaviour);
if (!condition) return null;
const orCondition: CompoudCondition = {
conditionType: 'or',
conditions: [],
};
for (const column of pattern.columns || []) {
orCondition.conditions.push(
_cloneDeepWith(condition, (expr: Expression) => {
if (expr.exprType == 'placeholder') {
return {
exprType: 'column',
columnName: column.name,
};
}
})
);
}
if (orCondition.conditions.length > 0) {
return orCondition;
}
}
getChildrenSqlCondition(source = null): Condition {
const conditions = _compact([
...this.childNodes.map(x => x.parseFilterCondition(source)),
...this.buildParentFilterConditions(),
this.getMutliColumnCondition(source),
]);
if (conditions.length == 0) {
return null;
}
if (conditions.length == 1) {
return conditions[0];
}
return {
conditionType: 'and',
conditions,
};
}
getOrderBy(table: TableInfo | ViewInfo | CollectionInfo): PerspectiveDataLoadProps['orderBy'] {
const res = _compact(
this.childNodes.map(node => {
const sort = this.nodeConfig?.sort?.find(x => x.columnName == node.columnName);
if (sort) {
return {
columnName: node.columnName,
order: sort.order,
};
}
})
);
if (res.length > 0) return res;
const pkColumns = (table as TableInfo)?.primaryKey?.columns.map(x => ({
columnName: x.columnName,
order: 'ASC' as 'ASC',
}));
if (pkColumns) return pkColumns;
const uqColumns = (table as CollectionInfo)?.uniqueKey;
if (uqColumns?.length >= 1) return uqColumns.map(x => ({ columnName: x.columnName, order: 'ASC' }));
const columns = (table as TableInfo | ViewInfo)?.columns;
if (columns) return [{ columnName: columns[0].columnName, order: 'ASC' }];
return [{ columnName: '_id', order: 'ASC' }];
}
getBaseTables() {
const res = [];
const table = this.getBaseTableFromThis();
if (table) res.push({ table, node: this });
for (const child of this.childNodes) {
if (!child.isChecked) continue;
res.push(...child.getBaseTables());
}
return res;
}
getBaseTableFromThis(): TableInfo | ViewInfo | CollectionInfo {
return null;
}
get filterInfo(): PerspectiveFilterColumnInfo {
return null;
}
findChildNodeByUniquePath(uniquePath: string[]) {
if (uniquePath.length == 0) {
return this;
}
const child = this.childNodes.find(x => x.codeName == uniquePath[0]);
return child?.findChildNodeByUniquePath(uniquePath.slice(1));
}
// findNodeByUniqueName(uniqueName: string): PerspectiveTreeNode {
// if (!uniqueName) return null;
// const uniquePath = uniqueName.split('::');
// if (uniquePath[0] != this.codeName) return null;
// return this.findChildNodeByUniquePath(uniquePath.slice(1));
// }
findNodeByDesignerId(designerId: string): PerspectiveTreeNode {
if (!this.designerId && !this.findByDesignerIdWithoutDesignerId) {
return null;
}
if (!designerId) {
return null;
}
if (designerId == this.designerId) {
return this;
}
for (const child of this.childNodes) {
const res = child.findNodeByDesignerId(designerId);
if (res) {
return res;
}
}
return null;
}
get supportsParentFilter() {
return (
(this.parentNode?.isRoot || this.parentNode?.supportsParentFilter) &&
this.parentNode?.databaseConfig?.conid == this.databaseConfig?.conid &&
this.parentNode?.databaseConfig?.database == this.databaseConfig?.database &&
this.engineType == 'sqldb' &&
this.parentNode?.engineType == 'sqldb'
);
}
get isParentFilter() {
return !!this.nodeConfig?.isParentFilter;
}
buildParentFilterConditions(): Condition[] {
const leafNodes = _compact(
(this.config?.nodes || [])
.filter(x => x.isParentFilter)
.map(x => this.rootNode.findNodeByDesignerId(x.designerId))
);
const conditions: Condition[] = _compact(
leafNodes.map(leafNode => {
if (leafNode == this) return null;
const select: Select = {
commandType: 'select',
from: {
name: leafNode.namedObject,
alias: 'pert_0',
relations: [],
},
selectAll: true,
};
let lastNode = leafNode;
let node = leafNode;
let index = 1;
let lastAlias = 'pert_0';
while (node?.parentNode && node?.parentNode?.designerId != this?.designerId) {
node = node.parentNode;
let alias = `pert_${index}`;
select.from.relations.push({
joinType: 'INNER JOIN',
alias,
name: node.namedObject,
conditions: lastNode.getParentJoinCondition(lastAlias, alias),
});
lastAlias = alias;
lastNode = node;
index += 1;
}
if (node?.parentNode?.designerId != this?.designerId) return null;
select.where = {
conditionType: 'and',
conditions: _compact([
...lastNode.getParentJoinCondition(lastAlias, this.namedObject.pureName),
leafNode.getChildrenSqlCondition({ alias: 'pert_0' }),
]),
};
return {
conditionType: 'exists',
subQuery: select,
};
})
);
return conditions;
}
getParentJoinCondition(alias: string, parentAlias: string): Condition[] {
return [];
}
get sortOrder() {
return this.parentNodeConfig?.sort?.find(x => x.columnName == this.columnName)?.order;
}
get sortOrderIndex() {
return this.parentNodeConfig?.sort?.length > 1
? _findIndex(this.parentNodeConfig?.sort, x => x.columnName == this.columnName)
: -1;
}
}
export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
foreignKey: ForeignKeyInfo;
refTable: TableInfo;
isView: boolean;
isTable: boolean;
constructor(
public column: ColumnInfo,
public table: TableInfo | ViewInfo,
dbs: MultipleDatabaseInfo,
config: PerspectiveConfig,
setConfig: ChangePerspectiveConfigFunc,
dataProvider: PerspectiveDataProvider,
defaultDatabaseConfig: PerspectiveDatabaseConfig,
parentNode: PerspectiveTreeNode,
designerId: string
) {
super(dbs, config, setConfig, parentNode, dataProvider, defaultDatabaseConfig, designerId);
this.isTable = !!this.db?.tables?.find(x => x.schemaName == table.schemaName && x.pureName == table.pureName);
this.isView = !!this.db?.views?.find(x => x.schemaName == table.schemaName && x.pureName == table.pureName);
this.foreignKey = (table as TableInfo)?.foreignKeys?.find(
fk => fk.columns.length == 1 && fk.columns[0].columnName == column.columnName
);
this.refTable = this.db.tables.find(
x => x.pureName == this.foreignKey?.refTableName && x.schemaName == this.foreignKey?.refSchemaName
);
}
get engineType() {
return this.parentNode.engineType;
}
matchChildRow(parentRow: any, childRow: any): boolean {
if (!this.foreignKey) return false;
return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName];
}
getChildMatchColumns() {
if (!this.foreignKey) return [];
return [this.foreignKey.columns[0].columnName];
}
getParentMatchColumns() {
if (!this.foreignKey) return [];
return [this.foreignKey.columns[0].refColumnName];
}
getParentJoinCondition(alias: string, parentAlias: string): Condition[] {
if (!this.foreignKey) return [];
return this.foreignKey.columns.map(column => {
const res: Condition = {
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName: column.columnName,
source: { alias: parentAlias },
},
right: {
exprType: 'column',
columnName: column.refColumnName,
source: { alias },
},
};
return res;
});
}
createReferenceConfigColumns(): PerspectiveReferenceConfig['columns'] {
return this.foreignKey?.columns?.map(col => ({
source: col.columnName,
target: col.refColumnName,
}));
}
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
if (!this.foreignKey) return null;
return {
schemaName: this.foreignKey.refSchemaName,
pureName: this.foreignKey.refTableName,
bindingColumns: [this.foreignKey.columns[0].refColumnName],
bindingValues: _uniqBy(
parentRows.map(row => [row[this.foreignKey.columns[0].columnName]]),
stableStringify
),
dataColumns: this.getDataLoadColumns(),
databaseConfig: this.databaseConfig,
orderBy: this.getOrderBy(this.refTable),
sqlCondition: this.getChildrenSqlCondition(),
engineType: 'sqldb',
};
}
get icon() {
if (this.isCircular) return 'img circular';
if (this.column.autoIncrement) return 'img autoincrement';
if (this.foreignKey) return 'img foreign-key';
return 'img column';
}
get codeName() {
return this.column.columnName;
}
get columnName() {
return this.column.columnName;
}
get fieldName() {
return this.codeName + 'Ref';
// return this.codeName ;
}
get title() {
return this.column.columnName;
}
get isExpandable() {
return !!this.foreignKey;
}
get isSortable() {
return true;
}
get filterBehaviour(): FilterBehaviour {
return detectSqlFilterBehaviour(this.column.dataType);
}
get isCircular() {
return !!this.parentNode?.parentNode?.hasTableCode(this.tableCode);
}
get isSecondaryChecked() {
return super.isCheckedColumn;
}
get isChecked() {
if (this.foreignKey) return this.isCheckedNode;
return super.isCheckedColumn;
}
get secondaryCheckable() {
return !!this.foreignKey;
}
toggleChecked(value?: boolean) {
if (this.foreignKey) {
this.toggleCheckedNode(value);
} else {
super.toggleChecked(value);
}
}
toggleSecondaryChecked(value?: boolean) {
super.toggleChecked(value == null ? !this.isSecondaryChecked : value);
}
generateChildNodes(): PerspectiveTreeNode[] {
if (!this.foreignKey) return [];
const tbl = this?.db?.tables?.find(
x => x.pureName == this.foreignKey?.refTableName && x.schemaName == this.foreignKey?.refSchemaName
);
return getTableChildPerspectiveNodes(
tbl,
this.dbs,
this.config,
this.setConfig,
this.dataProvider,
this.defaultDatabaseConfig,
this
);
}
getBaseTableFromThis() {
return this.refTable;
}
get filterInfo(): PerspectiveFilterColumnInfo {
return {
columnName: this.columnName,
filterBehaviour: this.filterBehaviour,
pureName: this.column.pureName,
schemaName: this.column.schemaName,
foreignKey: this.foreignKey,
};
}
parseFilterCondition(source = null): Condition {
const filter = this.getFilter();
if (!filter) return null;
const condition = parseFilter(filter, this.filterBehaviour);
if (!condition) return null;
return _cloneDeepWith(condition, (expr: Expression) => {
if (expr.exprType == 'placeholder') {
return {
exprType: 'column',
columnName: this.column.columnName,
source,
};
}
});
}
get headerTableAttributes() {
if (this.foreignKey) {
return {
schemaName: this.foreignKey.refSchemaName,
pureName: this.foreignKey.refTableName,
conid: this.databaseConfig.conid,
database: this.databaseConfig.database,
objectTypeField: this.table.objectTypeField,
};
}
return null;
}
get tableCode() {
if (this.foreignKey) {
return `${this.foreignKey.refSchemaName}|${this.foreignKey.refTableName}`;
}
return `${this.table.schemaName}|${this.table.pureName}`;
}
get namedObject(): NamedObjectInfo {
if (this.foreignKey) {
return {
schemaName: this.foreignKey.refSchemaName,
pureName: this.foreignKey.refTableName,
};
}
return null;
}
}
export class PerspectivePatternColumnNode extends PerspectiveTreeNode {
foreignKey: ForeignKeyInfo;
refTable: TableInfo;
constructor(
public table: TableInfo | ViewInfo | CollectionInfo,
public column: PerspectiveDataPatternColumn,
public tableColumn: ColumnInfo,
dbs: MultipleDatabaseInfo,
config: PerspectiveConfig,
setConfig: ChangePerspectiveConfigFunc,
dataProvider: PerspectiveDataProvider,
defaultDatabaseConfig: PerspectiveDatabaseConfig,
parentNode: PerspectiveTreeNode,
designerId: string
) {
super(dbs, config, setConfig, parentNode, dataProvider, defaultDatabaseConfig, designerId);
this.parentNodeConfig = this.tableNodeOrParent?.nodeConfig;
// console.log('PATTERN COLUMN', column);
}
get isChildColumn() {
return this.parentNode instanceof PerspectivePatternColumnNode;
}
get findByDesignerIdWithoutDesignerId() {
return this.isExpandable;
}
// matchChildRow(parentRow: any, childRow: any): boolean {
// console.log('MATCH PATTENR ROW', parentRow, childRow);
// return false;
// // if (!this.foreignKey) return false;
// // return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName];
// }
// getChildMatchColumns() {
// if (!this.foreignKey) return [];
// return [this.foreignKey.columns[0].columnName];
// }
// getParentMatchColumns() {
// if (!this.foreignKey) return [];
// return [this.foreignKey.columns[0].refColumnName];
// }
// getParentJoinCondition(alias: string, parentAlias: string): Condition[] {
// if (!this.foreignKey) return [];
// return this.foreignKey.columns.map(column => {
// const res: Condition = {
// conditionType: 'binary',
// operator: '=',
// left: {
// exprType: 'column',
// columnName: column.columnName,
// source: { alias: parentAlias },
// },
// right: {
// exprType: 'column',
// columnName: column.refColumnName,
// source: { alias },
// },
// };
// return res;
// });
// }
// createReferenceConfigColumns(): PerspectiveReferenceConfig['columns'] {
// return this.foreignKey?.columns?.map(col => ({
// source: col.columnName,
// target: col.refColumnName,
// }));
// }
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
// console.log('GETTING PATTERN', parentRows);
return null;
}
get generatesHiearchicGridColumn(): boolean {
// return true;
// console.log('generatesHiearchicGridColumn', this.parentTableNode?.nodeConfig?.checkedColumns, this.codeName + '::');
if (this.tableNodeOrParent?.nodeConfig?.checkedColumns?.find(x => x.startsWith(this.codeName + '::'))) {
return true;
}
// return false;
return this.hasCheckedJoinChild();
}
// get generatesHiearchicGridColumn() {
// // return this.config &&;
// }
get icon() {
if (this.column.types.includes('json')) {
return 'img json';
}
return 'img column';
}
get codeName() {
if (this.parentNode instanceof PerspectivePatternColumnNode) {
return `${this.parentNode.codeName}::${this.column.name}`;
}
return this.column.name;
}
get columnName() {
return this.column.name;
}
get fieldName() {
return this.column.name;
}
get title() {
return this.column.name;
}
get isExpandable() {
return this.column.columns.length > 0;
}
get isSortable() {
return !this.isChildColumn;
}
get filterBehaviour(): FilterBehaviour {
if (this.tableColumn) return detectSqlFilterBehaviour(this.tableColumn.dataType);
return mongoFilterBehaviour;
}
get preloadedLevelData() {
return true;
}
generatePatternChildNodes(): PerspectivePatternColumnNode[] {
return this.column.columns.map(
column =>
new PerspectivePatternColumnNode(
this.table,
column,
this.tableColumn,
this.dbs,
this.config,
this.setConfig,
this.dataProvider,
this.defaultDatabaseConfig,
this,
null
)
);
}
hasCheckedJoinChild() {
for (const node of this.childNodes) {
if (node instanceof PerspectivePatternColumnNode) {
if (node.hasCheckedJoinChild()) return true;
}
if (node.isCheckedNode) return true;
}
return false;
}
generateChildNodes(): PerspectiveTreeNode[] {
const patternChildren = this.generatePatternChildNodes();
const customs = [];
// console.log('GETTING CHILDREN', this.config.nodes, this.config.references);
for (const node of this.config.nodes) {
for (const ref of this.config.references) {
const validDesignerId = this.validParentDesignerId;
if (
(ref.sourceId == validDesignerId && ref.targetId == node.designerId) ||
(ref.targetId == validDesignerId && ref.sourceId == node.designerId)
) {
// console.log('TESTING REF', ref, this.codeName);
if (ref.columns.length != 1) continue;
// console.log('CP1');
if (
ref.sourceId == validDesignerId &&
this.codeName == getPerspectiveParentColumnName(ref.columns[0].source)
) {
if (ref.columns[0].target.includes('::')) continue;
} else if (
ref.targetId == validDesignerId &&
this.codeName == getPerspectiveParentColumnName(ref.columns[0].target)
) {
if (ref.columns[0].source.includes('::')) continue;
} else {
continue;
}
// console.log('CP2');
const newConfig: PerspectiveDatabaseConfig = { ...this.defaultDatabaseConfig };
if (node.conid) newConfig.conid = node.conid;
if (node.database) newConfig.database = node.database;
const db = this.dbs?.[newConfig.conid]?.[newConfig.database];
const table = db?.tables?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
const view = db?.views?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
const collection = db?.collections?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
const join: PerspectiveCustomJoinConfig = {
refNodeDesignerId: node.designerId,
referenceDesignerId: ref.designerId,
baseDesignerId: validDesignerId,
joinName: node.alias,
refTableName: node.pureName,
refSchemaName: node.schemaName,
conid: node.conid,
database: node.database,
columns:
ref.sourceId == validDesignerId
? ref.columns.map(col => ({ baseColumnName: col.source, refColumnName: col.target }))
: ref.columns.map(col => ({ baseColumnName: col.target, refColumnName: col.source })),
};
if (table || view || collection) {
customs.push(
new PerspectiveCustomJoinTreeNode(
join,
table || view || collection,
this.dbs,
this.config,
this.setConfig,
this.dataProvider,
this.defaultDatabaseConfig,
this,
node.designerId
)
);
}
}
}
}
return [...patternChildren, ...customs];
// return [];
// if (!this.foreignKey) return [];
// const tbl = this?.db?.tables?.find(
// x => x.pureName == this.foreignKey?.refTableName && x.schemaName == this.foreignKey?.refSchemaName
// );
// return getTableChildPerspectiveNodes(
// tbl,
// this.dbs,
// this.config,
// this.setConfig,
// this.dataProvider,
// this.databaseConfig,
// this
// );
}
get filterInfo(): PerspectiveFilterColumnInfo {
if (this.isChildColumn) {
return null;
}
return {
columnName: this.columnName,
filterBehaviour: this.filterBehaviour,
pureName: this.table.pureName,
schemaName: this.table.schemaName,
foreignKey: this.foreignKey,
};
}
parseFilterCondition(source = null): {} {
const filter = this.getFilter();
if (!filter) return null;
const condition = parseFilter(filter, mongoFilterBehaviour);
if (!condition) return null;
return _cloneDeepWith(condition, expr => {
if (expr.__placeholder__) {
return {
[this.columnName]: expr.__placeholder__,
};
}
});
}
// get headerTableAttributes() {
// if (this.foreignKey) {
// return {
// schemaName: this.foreignKey.refSchemaName,
// pureName: this.foreignKey.refTableName,
// conid: this.databaseConfig.conid,
// database: this.databaseConfig.database,
// };
// }
// return null;
// }
// get tableCode() {
// return `${this.collection.schemaName}|${this.table.pureName}`;
// }
// get namedObject(): NamedObjectInfo {
// if (this.foreignKey) {
// return {
// schemaName: this.foreignKey.refSchemaName,
// pureName: this.foreignKey.refTableName,
// };
// }
// return null;
// }
}
export class PerspectiveTableNode extends PerspectiveTreeNode {
constructor(
public table: TableInfo | ViewInfo | CollectionInfo,
dbs: MultipleDatabaseInfo,
config: PerspectiveConfig,
setConfig: ChangePerspectiveConfigFunc,
public dataProvider: PerspectiveDataProvider,
defaultDatabaseConfig: PerspectiveDatabaseConfig,
parentNode: PerspectiveTreeNode,
designerId: string
) {
super(dbs, config, setConfig, parentNode, dataProvider, defaultDatabaseConfig, designerId);
}
get engineType(): PerspectiveDatabaseEngineType {
return isCollectionInfo(this.table) ? 'docdb' : 'sqldb';
}
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
const isDocDb = isCollectionInfo(this.table);
return {
schemaName: this.table.schemaName,
pureName: this.table.pureName,
dataColumns: this.getDataLoadColumns(),
allColumns: isDocDb,
databaseConfig: this.databaseConfig,
orderBy: this.getOrderBy(this.table),
sqlCondition: this.getChildrenSqlCondition(),
engineType: isDocDb ? 'docdb' : 'sqldb',
};
}
get codeName() {
return this.table.schemaName ? `${this.table.schemaName}:${this.table.pureName}` : this.table.pureName;
}
get title() {
return this.nodeConfig?.alias || this.table.pureName;
}
get isExpandable() {
return true;
}
generateChildNodes(): PerspectiveTreeNode[] {
return getTableChildPerspectiveNodes(
this.table,
this.dbs,
this.config,
this.setConfig,
this.dataProvider,
this.defaultDatabaseConfig,
this
);
}
get icon() {
return 'img table';
}
getBaseTableFromThis() {
return this.table;
}
get headerTableAttributes() {
return {
schemaName: this.table.schemaName,
pureName: this.table.pureName,
conid: this.databaseConfig.conid,
database: this.databaseConfig.database,
objectTypeField: this.table.objectTypeField,
};
}
get tableCode() {
return `${this.table.schemaName}|${this.table.pureName}`;
}
get namedObject(): NamedObjectInfo {
return {
schemaName: this.table.schemaName,
pureName: this.table.pureName,
};
}
}
export class PerspectiveTableReferenceNode extends PerspectiveTableNode {
constructor(
public foreignKey: ForeignKeyInfo,
table: TableInfo,
dbs: MultipleDatabaseInfo,
config: PerspectiveConfig,
setConfig: ChangePerspectiveConfigFunc,
public dataProvider: PerspectiveDataProvider,
defaultDatabaseConfig: PerspectiveDatabaseConfig,
public isMultiple: boolean,
parentNode: PerspectiveTreeNode,
designerId: string
) {
super(table, dbs, config, setConfig, dataProvider, defaultDatabaseConfig, parentNode, designerId);
}
matchChildRow(parentRow: any, childRow: any): boolean {
if (!this.foreignKey) return false;
return parentRow[this.foreignKey.columns[0].refColumnName] == childRow[this.foreignKey.columns[0].columnName];
}
getChildMatchColumns() {
if (!this.foreignKey) return [];
return [this.foreignKey.columns[0].refColumnName];
}
getParentMatchColumns() {
if (!this.foreignKey) return [];
return [this.foreignKey.columns[0].columnName];
}
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
if (!this.foreignKey) return null;
return {
schemaName: this.table.schemaName,
pureName: this.table.pureName,
bindingColumns: [this.foreignKey.columns[0].columnName],
bindingValues: _uniqBy(
parentRows.map(row => [row[this.foreignKey.columns[0].refColumnName]]),
stableStringify
),
dataColumns: this.getDataLoadColumns(),
databaseConfig: this.databaseConfig,
orderBy: this.getOrderBy(this.table),
sqlCondition: this.getChildrenSqlCondition(),
engineType: 'sqldb',
};
}
createReferenceConfigColumns(): PerspectiveReferenceConfig['columns'] {
return this.foreignKey?.columns?.map(col => ({
source: col.refColumnName,
target: col.columnName,
}));
}
get columnTitle() {
return this.table.pureName;
}
get title() {
if (this.isMultiple) {
return `${super.title} (${this.foreignKey.columns.map(x => x.columnName).join(', ')})`;
}
return super.title;
}
get codeName() {
if (this.isMultiple) {
return `${super.codeName}-${this.foreignKey.columns.map(x => x.columnName).join('_')}`;
}
return super.codeName;
}
get isChecked() {
return this.isCheckedNode;
}
toggleChecked(value?: boolean) {
this.toggleCheckedNode(value);
}
getParentJoinCondition(alias: string, parentAlias: string): Condition[] {
if (!this.foreignKey) return [];
return this.foreignKey.columns.map(column => {
const res: Condition = {
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName: column.refColumnName,
source: { alias: parentAlias },
},
right: {
exprType: 'column',
columnName: column.columnName,
source: { alias },
},
};
return res;
});
}
}
export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode {
constructor(
public customJoin: PerspectiveCustomJoinConfig,
table: TableInfo | ViewInfo | CollectionInfo,
dbs: MultipleDatabaseInfo,
config: PerspectiveConfig,
setConfig: ChangePerspectiveConfigFunc,
public dataProvider: PerspectiveDataProvider,
defaultDatabaseConfig: PerspectiveDatabaseConfig,
parentNode: PerspectiveTreeNode,
designerId: string
) {
super(table, dbs, config, setConfig, dataProvider, defaultDatabaseConfig, parentNode, designerId);
}
matchChildRow(parentRow: any, childRow: any): boolean {
// console.log('MATCH ROW', parentRow, childRow);
for (const column of this.customJoin.columns) {
if (
!perspectiveValueMatcher(
parentRow[getPerspectiveMostNestedChildColumnName(column.baseColumnName)],
childRow[column.refColumnName]
)
) {
return false;
}
}
return true;
}
getChildMatchColumns() {
return this.customJoin.columns.map(x => x.baseColumnName);
}
getParentMatchColumns() {
return this.customJoin.columns.map(x => x.refColumnName);
}
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
// console.log('CUSTOM JOIN', this.customJoin);
// console.log('PARENT ROWS', parentRows);
// console.log('this.getDataLoadColumns()', this.getDataLoadColumns());
const isDocDb = isCollectionInfo(this.table);
// const bindingValues = [];
// for (const row of parentRows) {
// const rowBindingValueArrays = [];
// for (const col of this.customJoin.columns) {
// const path = col.baseColumnName.split('::');
// const values = [];
// function processSubpath(parent, subpath) {
// if (subpath.length == 0) {
// values.push(parent);
// return;
// }
// if (parent == null) {
// return;
// }
// const obj = parent[subpath[0]];
// if (_isArray(obj)) {
// for (const elem of obj) {
// processSubpath(elem, subpath.slice(1));
// }
// } else {
// processSubpath(obj, subpath.slice(1));
// }
// }
// processSubpath(row, path);
// rowBindingValueArrays.push(values);
// }
// const valueCount = Math.max(...rowBindingValueArrays.map(x => x.length));
// for (let i = 0; i < valueCount; i += 1) {
// const value = Array(this.customJoin.columns.length);
// for (let col = 0; col < this.customJoin.columns.length; col++) {
// value[col] = rowBindingValueArrays[col][i % rowBindingValueArrays[col].length];
// }
// bindingValues.push(value);
// }
// }
const bindingValues = parentRows.map(row =>
this.customJoin.columns.map(x => row[getPerspectiveMostNestedChildColumnName(x.baseColumnName)])
);
// console.log('bindingValues', bindingValues);
// console.log(
// 'bindingValues UNIQ',
// _uniqBy(bindingValues, x => JSON.stringify(x))
// );
return {
schemaName: this.table.schemaName,
pureName: this.table.pureName,
bindingColumns: this.getParentMatchColumns(),
bindingValues: _uniqBy(bindingValues, x => JSON.stringify(x)),
dataColumns: this.getDataLoadColumns(),
allColumns: isDocDb,
databaseConfig: this.databaseConfig,
orderBy: this.getOrderBy(this.table),
sqlCondition: this.getChildrenSqlCondition(),
// mongoCondition: isMongo ? this.getChildrenMongoCondition() : null,
engineType: isDocDb ? 'docdb' : 'sqldb',
};
}
// get title() {
// return this.customJoin.joinName || this.customJoin.refTableName;
// }
get icon() {
return 'icon custom-join';
}
get codeName() {
return this.customJoin.refNodeDesignerId;
}
get customJoinConfig(): PerspectiveCustomJoinConfig {
return this.customJoin;
}
get isChecked() {
return this.isCheckedNode;
}
toggleChecked(value?: boolean) {
this.toggleCheckedNode(value);
}
getParentJoinCondition(alias: string, parentAlias: string): Condition[] {
return this.customJoin.columns.map(column => {
const res: Condition = {
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName: column.baseColumnName,
source: { alias: parentAlias },
},
right: {
exprType: 'column',
columnName: column.refColumnName,
source: { alias },
},
};
return res;
});
}
}
function findDesignerIdForNode<T extends PerspectiveTreeNode>(
config: PerspectiveConfig,
parentNode: PerspectiveTreeNode,
nodeCreateFunc: (designerId: string) => T
): T {
const node = nodeCreateFunc(null);
const refColumns = node.createReferenceConfigColumns();
if (!refColumns?.length) {
return node;
}
const ref1 = config.references.find(
x =>
x.sourceId == parentNode.designerId &&
_isEqual(
refColumns.map(x => x.source),
x.columns.map(x => x.source)
) &&
_isEqual(
refColumns.map(x => x.target),
x.columns.map(x => x.target)
) &&
equalFullName(
config.nodes.find(n => n.designerId == x.targetId),
node.namedObject
)
);
if (ref1 && !parentNode.hasDesignerIdInIncestors(ref1.targetId)) {
// console.log('FOUND1', node.title, ref1.targetId, refColumns);
return nodeCreateFunc(ref1.targetId);
}
const ref2 = config.references.find(
x =>
x.targetId == parentNode.designerId &&
_isEqual(
refColumns.map(x => x.target),
x.columns.map(x => x.source)
) &&
_isEqual(
refColumns.map(x => x.source),
x.columns.map(x => x.target)
) &&
equalFullName(
config.nodes.find(n => n.designerId == x.sourceId),
node.namedObject
)
);
if (ref2 && !parentNode.hasDesignerIdInIncestors(ref2.sourceId)) {
// console.log('FOUND2', node.title, ref2.sourceId, refColumns);
return nodeCreateFunc(ref2.sourceId);
}
return node;
}
export function getTableChildPerspectiveNodes(
table: TableInfo | ViewInfo | CollectionInfo,
dbs: MultipleDatabaseInfo,
config: PerspectiveConfig,
setConfig: ChangePerspectiveConfigFunc,
dataProvider: PerspectiveDataProvider,
defaultDatabaseConfig: PerspectiveDatabaseConfig,
parentNode: PerspectiveTreeNode
) {
if (!table) return [];
const db = parentNode.db;
const pattern = dataProvider?.dataPatterns?.[parentNode.designerId];
const tableOrView = isTableInfo(table) || isViewInfo(table) ? table : null;
const columnNodes =
tableOrView?.columns?.map(col =>
findDesignerIdForNode(config, parentNode, designerId =>
pattern?.columns?.find(x => x.name == col.columnName)?.types.includes('json')
? new PerspectivePatternColumnNode(
table,
pattern?.columns?.find(x => x.name == col.columnName),
col,
dbs,
config,
setConfig,
dataProvider,
defaultDatabaseConfig,
parentNode,
designerId
)
: new PerspectiveTableColumnNode(
col,
tableOrView,
dbs,
config,
setConfig,
dataProvider,
defaultDatabaseConfig,
parentNode,
designerId
)
)
) ||
pattern?.columns?.map(col =>
findDesignerIdForNode(
config,
parentNode,
designerId =>
new PerspectivePatternColumnNode(
table,
col,
null,
dbs,
config,
setConfig,
dataProvider,
defaultDatabaseConfig,
parentNode,
designerId
)
)
) ||
[];
// if (!columnNodes.find(x => x.isChecked)) {
// const circularColumns = columnNodes.filter(x => x.isCircular).map(x => x.columnName);
// const defaultColumns = getPerspectiveDefaultColumns(table, db, circularColumns);
// for (const node of columnNodes) {
// if (defaultColumns.includes(node.columnName)) {
// no
// }
// }
// }
const res = [];
res.push(...columnNodes);
const dependencies = [];
if (db && (table as TableInfo)?.dependencies) {
for (const fk of (table as TableInfo)?.dependencies) {
const tbl = db.tables.find(x => x.pureName == fk.pureName && x.schemaName == fk.schemaName);
if (tbl) {
const isMultiple =
(table as TableInfo)?.dependencies.filter(x => x.pureName == fk.pureName && x.schemaName == fk.schemaName)
.length >= 2;
dependencies.push(
findDesignerIdForNode(
config,
parentNode,
designerId =>
new PerspectiveTableReferenceNode(
fk,
tbl,
dbs,
config,
setConfig,
dataProvider,
defaultDatabaseConfig,
isMultiple,
parentNode,
designerId
)
)
);
}
}
}
res.push(..._sortBy(dependencies, 'title'));
const customs = [];
for (const node of config.nodes) {
if (node.designerId == parentNode.parentNode?.designerId || res.find(x => x.designerId == node.designerId)) {
// already used as FK
continue;
}
for (const ref of config.references) {
if (
(ref.sourceId == parentNode.designerId && ref.targetId == node.designerId) ||
(ref.targetId == parentNode.designerId && ref.sourceId == node.designerId)
) {
if (ref.columns.find(x => x.source.includes('::') || x.target.includes('::'))) {
continue;
}
const newConfig: PerspectiveDatabaseConfig = { ...defaultDatabaseConfig };
if (node.conid) newConfig.conid = node.conid;
if (node.database) newConfig.database = node.database;
const db = dbs?.[newConfig.conid]?.[newConfig.database];
const table = db?.tables?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
const view = db?.views?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
const collection = db?.collections?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
const join: PerspectiveCustomJoinConfig = {
refNodeDesignerId: node.designerId,
referenceDesignerId: ref.designerId,
baseDesignerId: parentNode.designerId,
joinName: node.alias,
refTableName: node.pureName,
refSchemaName: node.schemaName,
conid: node.conid,
database: node.database,
columns:
ref.sourceId == parentNode.designerId
? ref.columns.map(col => ({ baseColumnName: col.source, refColumnName: col.target }))
: ref.columns.map(col => ({ baseColumnName: col.target, refColumnName: col.source })),
};
if (table || view || collection) {
customs.push(
new PerspectiveCustomJoinTreeNode(
join,
table || view || collection,
dbs,
config,
setConfig,
dataProvider,
defaultDatabaseConfig,
parentNode,
node.designerId
)
);
}
}
}
}
res.push(..._sortBy(customs, 'title'));
return res;
}