From bd4dbabdabc7dbe16f81f1028914d9063d070f39 Mon Sep 17 00:00:00 2001 From: datomo Date: Thu, 29 Feb 2024 15:26:32 +0100 Subject: [PATCH] started fixing schema editing --- src/app/models/catalog.model.ts | 5 + src/app/services/catalog.service.ts | 625 +++++++++--------- .../edit-source-columns.component.html | 6 +- .../edit-source-columns.component.ts | 33 +- .../edit-tables/edit-tables.component.ts | 20 +- 5 files changed, 347 insertions(+), 342 deletions(-) diff --git a/src/app/models/catalog.model.ts b/src/app/models/catalog.model.ts index 3c092a7b..3a9f6126 100644 --- a/src/app/models/catalog.model.ts +++ b/src/app/models/catalog.model.ts @@ -88,6 +88,11 @@ export interface KeyModel extends IdEntity { isPrimary: boolean; } +export interface ForeignKeyModel extends KeyModel { + referencedIds: number[]; + referencedEntityId: number; +} + export interface ConstraintModel extends IdEntity { keyId: number; type: string; diff --git a/src/app/services/catalog.service.ts b/src/app/services/catalog.service.ts index 91fd798f..e945a43a 100644 --- a/src/app/services/catalog.service.ts +++ b/src/app/services/catalog.service.ts @@ -2,22 +2,22 @@ import {effect, Injectable, signal, untracked, WritableSignal} from '@angular/co import {HttpClient} from '@angular/common/http'; import {WebuiSettingsService} from './webui-settings.service'; import { - AdapterTemplateModel, - AllocationColumnModel, - AllocationEntityModel, - AllocationPartitionModel, - AllocationPlacementModel, - AssetsModel, - CatalogState, - ColumnModel, - ConstraintModel, - EntityModel, - EntityType, - FieldModel, - IdEntity, - KeyModel, - LogicalSnapshotModel, - NamespaceModel + AdapterTemplateModel, + AllocationColumnModel, + AllocationEntityModel, + AllocationPartitionModel, + AllocationPlacementModel, + AssetsModel, + CatalogState, + ColumnModel, + ConstraintModel, + EntityModel, + EntityType, + FieldModel, + IdEntity, + KeyModel, + LogicalSnapshotModel, + NamespaceModel } from '../models/catalog.model'; import {DataModel} from '../models/ui-request.model'; import {SidebarNode} from '../models/sidebar-node.model'; @@ -29,358 +29,363 @@ import {AuthService} from './auth.service'; import {WebSocket} from './webSocket'; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class CatalogService { - public listener: WritableSignal = signal(this); - public state: WritableSignal = signal(CatalogState.INIT); - - private httpUrl = this._settings.getConnection('crud.rest'); - - private assets: AssetsModel; - - private snapshot: LogicalSnapshotModel; - public readonly namespaces: WritableSignal> = signal(new Map()); - public readonly namespacesNames: WritableSignal> = signal(new Map()); - public readonly entities: WritableSignal> = signal(new Map()); - public readonly fields: WritableSignal> = signal(new Map()); - public readonly fieldNames: WritableSignal> = signal(new Map()); - public readonly keys: WritableSignal> = signal(new Map()); - public readonly constraints: WritableSignal> = signal(new Map()); - - public readonly placements: WritableSignal> = signal(new Map()); - public readonly partitions: WritableSignal> = signal(new Map()); - public readonly allocations: WritableSignal> = signal(new Map()); - public readonly allocationColumns: WritableSignal> = signal(new Map()); - - public readonly adapters: WritableSignal> = signal(new Map()); - public readonly adapterTemplates: WritableSignal> = signal(new Map()); // typescript uses by reference comparisons, so this is necessary - - constructor( - private _http: HttpClient, - private _settings: WebuiSettingsService, - private _types: DbmsTypesService, - private _auth: AuthService - ) { - this.state.set(CatalogState.LOADING); - - effect(() => { - const id = this._auth.id(); - if (!id) { - return; - } - untracked(() => { - this.initWebsocket(id, this._auth.websocket); - }); - }); - - - this.updateIfNecessary().pipe(combineLatestWith(this.updateAssets()), combineLatestWith(_types.getTypes())).subscribe(() => { - this.state.set(CatalogState.UP_TO_DATE); - }); - } - - private initWebsocket(id: string, websocket: WebSocket) { - websocket.onMessage().subscribe({ - next: (snapshot: LogicalSnapshotModel) => { - console.log(snapshot); - this.updateSnapshot(snapshot); - } - }); - } - - getSnapshot(): Observable { - return this._http.get(`${this.httpUrl}/getSnapshot`).pipe(map((snapshot: LogicalSnapshotModel) => { - this.updateSnapshot(snapshot); - return this; - })); - } - - private updateSnapshot(snapshot: LogicalSnapshotModel) { - this.snapshot = snapshot; - console.log(this.snapshot); - - this.namespaces.set(this.toIdMap(snapshot.namespaces)); - this.namespacesNames.set(this.toNameMap(snapshot.namespaces)); - this.entities.set(this.toIdMap(this.snapshot.entities)); - this.fields.set(this.toIdMap(this.snapshot.fields)); - this.fieldNames.set(this.toNameMap(this.snapshot.fields)); - this.keys.set(this.toIdMap(this.snapshot.keys)); - this.constraints.set(this.toIdMap(this.snapshot.constraints)); - this.placements.set(this.toIdMap(this.snapshot.placements)); - this.partitions.set(this.toIdMap(this.snapshot.partitions)); - this.allocations.set(this.toIdMap(this.snapshot.allocations)); - this.allocationColumns.set(this.toIdMap(this.snapshot.allocColumns)); - this.adapters.set(this.toIdMap(this.snapshot.adapters)); - this.adapterTemplates.set(new Map(this.snapshot.adapterTemplates.map(t => [t.adapterName + '_' + t.adapterType, t]))); - - this.listener.set(this); // notify - this.state.set(CatalogState.UP_TO_DATE); - } - - updateIfNecessary(): Observable { - const sub: Subject = new Subject(); - this._http.get(`${this.httpUrl}/getCurrentSnapshot`).subscribe((id: number) => { - this.getSnapshot().subscribe(() => { - sub.next(this); - }); - }); - return sub.pipe(); - } + public listener: WritableSignal = signal(this); + public state: WritableSignal = signal(CatalogState.INIT); + + private httpUrl = this._settings.getConnection('crud.rest'); + + private assets: AssetsModel; + + private snapshot: LogicalSnapshotModel; + public readonly namespaces: WritableSignal> = signal(new Map()); + public readonly namespacesNames: WritableSignal> = signal(new Map()); + public readonly entities: WritableSignal> = signal(new Map()); + public readonly fields: WritableSignal> = signal(new Map()); + public readonly fieldNames: WritableSignal> = signal(new Map()); + public readonly keys: WritableSignal> = signal(new Map()); + public readonly constraints: WritableSignal> = signal(new Map()); + + public readonly placements: WritableSignal> = signal(new Map()); + public readonly partitions: WritableSignal> = signal(new Map()); + public readonly allocations: WritableSignal> = signal(new Map()); + public readonly allocationColumns: WritableSignal> = signal(new Map()); + + public readonly adapters: WritableSignal> = signal(new Map()); + public readonly adapterTemplates: WritableSignal> = signal(new Map()); // typescript uses by reference comparisons, so this is necessary + + constructor( + private _http: HttpClient, + private _settings: WebuiSettingsService, + private _types: DbmsTypesService, + private _auth: AuthService + ) { + this.state.set(CatalogState.LOADING); + + effect(() => { + const id = this._auth.id(); + if (!id) { + return; + } + untracked(() => { + this.initWebsocket(id, this._auth.websocket); + }); + }); + + + this.updateIfNecessary().pipe(combineLatestWith(this.updateAssets()), combineLatestWith(_types.getTypes())).subscribe(() => { + this.state.set(CatalogState.UP_TO_DATE); + }); + } - getSchemaTree(routerLinkRoot: string, views: boolean, depth: number, schemaEdit?: boolean, dataModels: DataModel[] = [DataModel.RELATIONAL, DataModel.DOCUMENT, DataModel.GRAPH]) { - return this.buildSchemaTree(routerLinkRoot, views, depth, schemaEdit, dataModels); + private initWebsocket(id: string, websocket: WebSocket) { + websocket.onMessage().subscribe({ + next: (snapshot: LogicalSnapshotModel) => { + console.log(snapshot); + this.updateSnapshot(snapshot); + } + }); + } - } + getSnapshot(): Observable { + return this._http.get(`${this.httpUrl}/getSnapshot`).pipe(map((snapshot: LogicalSnapshotModel) => { + this.updateSnapshot(snapshot); + return this; + })); + } - private toIdMap(idEntities: T[]) { - return new Map(idEntities.map(n => [n.id, n])); - } + private updateSnapshot(snapshot: LogicalSnapshotModel) { + this.snapshot = snapshot; + console.log(this.snapshot); + + this.namespaces.set(this.toIdMap(snapshot.namespaces)); + this.namespacesNames.set(this.toNameMap(snapshot.namespaces)); + this.entities.set(this.toIdMap(this.snapshot.entities)); + this.fields.set(this.toIdMap(this.snapshot.fields)); + this.fieldNames.set(this.toNameMap(this.snapshot.fields)); + this.keys.set(this.toIdMap(this.snapshot.keys)); + this.constraints.set(this.toIdMap(this.snapshot.constraints)); + this.placements.set(this.toIdMap(this.snapshot.placements)); + this.partitions.set(this.toIdMap(this.snapshot.partitions)); + this.allocations.set(this.toIdMap(this.snapshot.allocations)); + this.allocationColumns.set(this.toIdMap(this.snapshot.allocColumns)); + this.adapters.set(this.toIdMap(this.snapshot.adapters)); + this.adapterTemplates.set(new Map(this.snapshot.adapterTemplates.map(t => [t.adapterName + '_' + t.adapterType, t]))); + + this.listener.set(this); // notify + this.state.set(CatalogState.UP_TO_DATE); + } - private toNameMap(idEntities: T[]) { - return new Map(idEntities.map(n => [n.name, n])); - } + updateIfNecessary(): Observable { + const sub: Subject = new Subject(); + this._http.get(`${this.httpUrl}/getCurrentSnapshot`).subscribe((id: number) => { + this.getSnapshot().subscribe(() => { + sub.next(this); + }); + }); + return sub.pipe(); + } + getSchemaTree(routerLinkRoot: string, views: boolean, depth: number, schemaEdit?: boolean, dataModels: DataModel[] = [DataModel.RELATIONAL, DataModel.DOCUMENT, DataModel.GRAPH]) { + return this.buildSchemaTree(routerLinkRoot, views, depth, schemaEdit, dataModels); - getEntity(entityId: number): EntityModel { - return this.entities().get(entityId); - } + } - getNamespaceNames(): string[] { - return Array.from(this.namespaces().values()).map(n => n.name); - } + private toIdMap(idEntities: T[]) { + return new Map(idEntities.map(n => [n.id, n])); + } - getNamespaceFromName(name: string): NamespaceModel { - return this.namespacesNames().get(name); - } + private toNameMap(idEntities: T[]) { + return new Map(idEntities.map(n => [n.name, n])); + } - getNamespaceFromId(id: number): NamespaceModel { - return this.namespaces().get(id); - } - getNamespaces(): NamespaceModel[] { - return Array.from(this.namespaces().values()); - } + getEntity(entityId: number): EntityModel { + return this.entities().get(entityId); + } - getEntityFromName(namespace: string, name: string): EntityModel { - const namespaces = Array.from(this.namespaces().values()).filter(n => (n.caseSensitive ? n.name === namespace : n.name.toLowerCase() === namespace.toLowerCase()) - || n.dataModel === DataModel.GRAPH && name.toLowerCase() === n.name.toLowerCase() || namespace.toLowerCase() === n.name.toLowerCase()); - if (namespaces.length === 0) { - return null; + getNamespaceNames(): string[] { + return Array.from(this.namespaces().values()).map(n => n.name); } - return Array.from(this.entities().values()).filter(e => e.namespaceId === namespaces[0].id && e.name === name || (e.dataModel === DataModel.GRAPH && namespace.toLowerCase() === e.name.toLowerCase()))[0]; - } + getNamespaceFromName(name: string): NamespaceModel { + return this.namespacesNames().get(name); + } - getFullEntityName(entityId: number): String { - const entity = this.entities().get(entityId); - const namespace = this.namespaces().get(entity.namespaceId); - return namespace.name + '.' + entity.name; - } + getNamespaceFromId(id: number): NamespaceModel { + return this.namespaces().get(id); + } - //// UTIL + getNamespaces(): NamespaceModel[] { + return Array.from(this.namespaces().values()); + } + + getEntityFromName(namespace: string, name: string): EntityModel { + const namespaces = Array.from(this.namespaces().values()).filter(n => (n.caseSensitive ? n.name === namespace : n.name.toLowerCase() === namespace.toLowerCase()) + || n.dataModel === DataModel.GRAPH && name.toLowerCase() === n.name.toLowerCase() || namespace.toLowerCase() === n.name.toLowerCase()); + if (namespaces.length === 0) { + return null; + } + + return Array.from(this.entities().values()).filter(e => e.namespaceId === namespaces[0].id && e.name === name || (e.dataModel === DataModel.GRAPH && namespace.toLowerCase() === e.name.toLowerCase()))[0]; + } + + getFullEntityName(entityId: number): String { + const entity = this.entities().get(entityId); + const namespace = this.namespaces().get(entity.namespaceId); + return namespace.name + '.' + entity.name; + } + + //// UTIL private buildSchemaTree(routerLinkRoot: string, views: boolean, depth: number, schemaEdit: boolean, dataModels: DataModel[]): SidebarNode[] { - const nodes: SidebarNode[] = []; - for (const namespace of this.namespaces().values()) { - const namespaceNode = new SidebarNode(namespace.name, namespace.name, this.getNamespaceIcon(namespace.dataModel) + ' me-1', ''); - - if (depth > 1) { - switch (namespace.dataModel) { - case DataModel.DOCUMENT: - this.attachDocumentTree(namespace, namespaceNode, routerLinkRoot, depth, views); - break; - case DataModel.RELATIONAL: - this.attachRelationalTree(namespace, namespaceNode, routerLinkRoot, depth, views); - break; - case DataModel.GRAPH: - namespaceNode.routerLink = routerLinkRoot + '' + namespace.name; - break; - } - } - nodes.push(namespaceNode); - } - - return nodes; - } - - private attachDocumentTree(namespace: NamespaceModel, namespaceNode: SidebarNode, routerLinkRoot: string, depth: number, views: boolean) { - const nodes: SidebarNode[] = []; - const collections: EntityModel[] = Array.from(this.entities().values()).filter(e => e.namespaceId === namespace.id); - - for (const collection of collections) { - - let icon = this.getNamespaceIcon(collection.dataModel); - switch (collection.entityType) { - case EntityType.SOURCE: - icon = this.assets.SOURCE_ICON; - break; - case EntityType.VIEW: - icon = this.assets.VIEW_ICON; - break; - } - const collectionTree = new SidebarNode(namespace.name + '.' + collection.name, collection.name, icon + ' me-1', routerLinkRoot + namespace.name + '.' + collection.name); - - nodes.push(collectionTree); - } - namespaceNode.children.push(...nodes); - } - - private attachRelationalTree(namespace: NamespaceModel, namespaceNode: SidebarNode, routerLinkRoot: string, depth: number, views: boolean) { - const nodes: SidebarNode[] = []; - const tables: EntityModel[] = Array.from(this.entities().values()).filter(t => t.namespaceId === namespace.id); - for (const table of tables) { - let icon = this.assets.TABLE_ICON; - - switch (table.entityType) { - case EntityType.SOURCE: - icon = this.assets.SOURCE_ICON; - break; - case EntityType.VIEW: - case EntityType.MATERIALIZED_VIEW: - icon = this.assets.VIEW_ICON; - break; - } - - const tableNode = new SidebarNode(namespace.name + '.' + table.name, table.name, icon + ' me-1', routerLinkRoot + namespace.name + '.' + table.name); - - if (depth > 2) { - const columns = Array.from(this.snapshot.fields.values()).filter(f => f.entityId === table.id); - for (const column of columns) { - tableNode.children.push(new SidebarNode(namespace.name + '.' + table.name + '.' + column.name, column.name, icon, routerLinkRoot)); + const nodes: SidebarNode[] = []; + for (const namespace of this.namespaces().values()) { + const namespaceNode = new SidebarNode(namespace.name, namespace.name, this.getNamespaceIcon(namespace.dataModel) + ' me-1', ''); + + if (depth > 1) { + switch (namespace.dataModel) { + case DataModel.DOCUMENT: + this.attachDocumentTree(namespace, namespaceNode, routerLinkRoot, depth, views); + break; + case DataModel.RELATIONAL: + this.attachRelationalTree(namespace, namespaceNode, routerLinkRoot, depth, views); + break; + case DataModel.GRAPH: + namespaceNode.routerLink = routerLinkRoot + '' + namespace.name; + break; + } + } + nodes.push(namespaceNode); } - } - nodes.push(tableNode); + return nodes; } - namespaceNode.children.push(...nodes); - namespaceNode.routerLink = ''; - } - private updateAssets() { - return new Observable(subscriber => this._http.get(`${this.httpUrl}/getAssetsDefinition`).subscribe((assets: AssetsModel) => { - this.assets = assets; - subscriber.next({}); + private attachDocumentTree(namespace: NamespaceModel, namespaceNode: SidebarNode, routerLinkRoot: string, depth: number, views: boolean) { + const nodes: SidebarNode[] = []; + const collections: EntityModel[] = Array.from(this.entities().values()).filter(e => e.namespaceId === namespace.id); - return { - unsubscribe() { + for (const collection of collections) { + + let icon = this.getNamespaceIcon(collection.dataModel); + switch (collection.entityType) { + case EntityType.SOURCE: + icon = this.assets.SOURCE_ICON; + break; + case EntityType.VIEW: + icon = this.assets.VIEW_ICON; + break; + } + const collectionTree = new SidebarNode(namespace.name + '.' + collection.name, collection.name, icon + ' me-1', routerLinkRoot + namespace.name + '.' + collection.name); + + nodes.push(collectionTree); } - }; - })); - } + namespaceNode.children.push(...nodes); + } + + private attachRelationalTree(namespace: NamespaceModel, namespaceNode: SidebarNode, routerLinkRoot: string, depth: number, views: boolean) { + const nodes: SidebarNode[] = []; + const tables: EntityModel[] = Array.from(this.entities().values()).filter(t => t.namespaceId === namespace.id); + for (const table of tables) { + let icon = this.assets.TABLE_ICON; + + switch (table.entityType) { + case EntityType.SOURCE: + icon = this.assets.SOURCE_ICON; + break; + case EntityType.VIEW: + case EntityType.MATERIALIZED_VIEW: + icon = this.assets.VIEW_ICON; + break; + } + + const tableNode = new SidebarNode(namespace.name + '.' + table.name, table.name, icon + ' me-1', routerLinkRoot + namespace.name + '.' + table.name); + + if (depth > 2) { + const columns = Array.from(this.snapshot.fields.values()).filter(f => f.entityId === table.id); + for (const column of columns) { + tableNode.children.push(new SidebarNode(namespace.name + '.' + table.name + '.' + column.name, column.name, icon, routerLinkRoot)); + } + } + nodes.push(tableNode); + + } + namespaceNode.children.push(...nodes); + namespaceNode.routerLink = ''; + } + + private updateAssets() { + return new Observable(subscriber => this._http.get(`${this.httpUrl}/getAssetsDefinition`).subscribe((assets: AssetsModel) => { + this.assets = assets; + subscriber.next({}); + + return { + unsubscribe() { + } + }; + })); + } private getNamespaceIcon(dataModel: DataModel): string { switch (dataModel) { case DataModel.DOCUMENT: - return this.assets.DOCUMENT_ICON; + return this.assets.DOCUMENT_ICON; case DataModel.RELATIONAL: - return this.assets.RELATIONAL_ICON; + return this.assets.RELATIONAL_ICON; case DataModel.GRAPH: - return this.assets.GRAPH_ICON; + return this.assets.GRAPH_ICON; + } } - } - getEntities(namespaceId: number): EntityModel[] { - return Array.from(this.entities().values()).filter(n => n.namespaceId === namespaceId); - } + getEntities(namespaceId: number): EntityModel[] { + return Array.from(this.entities().values()).filter(n => n.namespaceId === namespaceId); + } + + getColumns(entityId: number): ColumnModel[] { + return Array.from(this.fields().values()).filter(f => f.entityId === entityId).map(f => f); + } - getColumns(entityId: number): ColumnModel[] { - return Array.from(this.fields().values()).filter(f => f.entityId === entityId).map(f => f); - } + getPrimaryKey(entityId: number): KeyModel { + return Array.from(this.keys().values()).filter(k => k.isPrimary && k.entityId === entityId)[0]; + } - getPrimaryKey(entityId: number): KeyModel { - return Array.from(this.keys().values()).filter(k => k.isPrimary && k.entityId === entityId)[0]; - } + getKey(keyId: number): KeyModel { + return this.keys().get(keyId); + } - getKey(keyId: number): KeyModel { - return this.keys().get(keyId); - } + getKeys(entityId: number): KeyModel[] { + return Array.from(this.keys().values()).filter(k => k.entityId === entityId); + } - getKeys(entityId: number): KeyModel[] { - return Array.from(this.keys().values()).filter(k => k.entityId === entityId); - } + getConstraint(constraintId: number): ConstraintModel { + return this.constraints().get(constraintId); + } - getConstraint(constraintId: number): ConstraintModel { - return this.constraints().get(constraintId); - } + getConstraintName(keyId: number): string { + const constraintNames = Array.from(this.constraints().values()).filter(c => c.keyId === keyId).map(c => c.name); + return constraintNames.length >= 1 ? constraintNames[0] : null; + } - getConstraints(entityId: number): ConstraintModel[] { - const constraints = Array.from(this.constraints().values()); - const keys = Array.from(this.keys().values()).filter(k => k.entityId === entityId).map(k => k.id); - return constraints.filter(c => keys.includes(c.keyId)); - } + getConstraints(entityId: number): ConstraintModel[] { + const constraints = Array.from(this.constraints().values()); + const keys = Array.from(this.keys().values()).filter(k => k.entityId === entityId).map(k => k.id); + return constraints.filter(c => keys.includes(c.keyId)); + } - getPlacements(entityId: number): AllocationPlacementModel[] { - return Array.from(this.placements().values()).filter(p => p.logicalEntityId === entityId); - } + getPlacements(entityId: number): AllocationPlacementModel[] { + return Array.from(this.placements().values()).filter(p => p.logicalEntityId === entityId); + } - getPartitions(entityId: number): AllocationPartitionModel[] { - return Array.from(this.partitions().values()).filter(p => p.logicalEntityId === entityId); - } + getPartitions(entityId: number): AllocationPartitionModel[] { + return Array.from(this.partitions().values()).filter(p => p.logicalEntityId === entityId); + } - getAllocColumns(placemenId: number): AllocationColumnModel[] { - return Array.from(this.allocationColumns().values()).filter(a => a.placementId === placemenId); - } + getAllocColumns(placemenId: number): AllocationColumnModel[] { + return Array.from(this.allocationColumns().values()).filter(a => a.placementId === placemenId); + } - getAllocColumn(id: number): AllocationColumnModel { - return Array.from(this.allocationColumns().values()).filter(c => c.id === id)[0]; - } + getAllocColumn(id: number): AllocationColumnModel { + return Array.from(this.allocationColumns().values()).filter(c => c.id === id)[0]; + } - getAdapter(adapterId: number) { - return this.adapters().get(adapterId); - } + getAdapter(adapterId: number) { + return this.adapters().get(adapterId); + } - getAvailableStoresForIndexes(entityId: number): AdapterModel[] { - const adapterIds = Array.from(this.placements().values()).map(p => p.adapterId); - return Array.from(this.adapters().values()).filter(a => adapterIds.includes(a.id)).filter(a => a.type === AdapterType.STORE); - } + getAvailableStoresForIndexes(entityId: number): AdapterModel[] { + const adapterIds = Array.from(this.placements().values()).map(p => p.adapterId); + return Array.from(this.adapters().values()).filter(a => adapterIds.includes(a.id)).filter(a => a.type === AdapterType.STORE); + } - getStores(): AdapterModel[] { - return Array.from(this.adapters().values()).filter(a => { - return a.type === AdapterType.STORE; - }); - } + getStores(): AdapterModel[] { + return Array.from(this.adapters().values()).filter(a => { + return a.type === AdapterType.STORE; + }); + } - getSources() { - return Array.from(this.adapters().values()).filter(a => { - return a.type === AdapterType.SOURCE; - }); - } + getSources() { + return Array.from(this.adapters().values()).filter(a => { + return a.type === AdapterType.SOURCE; + }); + } - getAdapterTemplate(adapterName: string, type: AdapterType): AdapterTemplateModel { - return this.adapterTemplates().get(adapterName + '_' + type); - } + getAdapterTemplate(adapterName: string, type: AdapterType): AdapterTemplateModel { + return this.adapterTemplates().get(adapterName + '_' + type); + } - getAdapterTemplates() { - return Array.from(this.adapterTemplates().values()); - } + getAdapterTemplates() { + return Array.from(this.adapterTemplates().values()); + } - getLogicalField(id: number) { - return Array.from(this.fields().values()).filter(f => f.id === id)[0]; - } + getLogicalField(id: number) { + return Array.from(this.fields().values()).filter(f => f.id === id)[0]; + } - getLogicalColumn(id: number) { - const column = this.getLogicalField(id); - return column as ColumnModel; - } + getLogicalColumn(id: number) { + const column = this.getLogicalField(id); + return column as ColumnModel; + } - getAllocsOfPlacement(logicalId: number, allocId: number, adapterId: number): AllocationPartitionModel[] { - const partitions = Array.from(this.partitions().values()).filter(p => p.logicalEntityId === logicalId); - const allocPartitionIds = Array.from(this.allocations().values()).filter(a => a.id === allocId).map(a => a.partitionId); + getAllocsOfPlacement(logicalId: number, allocId: number, adapterId: number): AllocationPartitionModel[] { + const partitions = Array.from(this.partitions().values()).filter(p => p.logicalEntityId === logicalId); + const allocPartitionIds = Array.from(this.allocations().values()).filter(a => a.id === allocId).map(a => a.partitionId); - return partitions.filter(p => allocPartitionIds.includes(p.id)); - } + return partitions.filter(p => allocPartitionIds.includes(p.id)); + } - getAllocations(logicalEntityId: number) { - return Array.from(this.allocations().values()).filter(a => a.logicalEntityId === logicalEntityId); - } + getAllocations(logicalEntityId: number) { + return Array.from(this.allocations().values()).filter(a => a.logicalEntityId === logicalEntityId); + } - getEntityFromIdName(namespaceId: number, entityName: string) { - return Array.from(this.entities().values()).filter(e => e.namespaceId === namespaceId && e.name === entityName)[0]; - } + getEntityFromIdName(namespaceId: number, entityName: string) { + return Array.from(this.entities().values()).filter(e => e.namespaceId === namespaceId && e.name === entityName)[0]; + } } diff --git a/src/app/views/schema-editing/edit-source-columns/edit-source-columns.component.html b/src/app/views/schema-editing/edit-source-columns/edit-source-columns.component.html index 04e71b72..c62937fd 100644 --- a/src/app/views/schema-editing/edit-source-columns/edit-source-columns.component.html +++ b/src/app/views/schema-editing/edit-source-columns/edit-source-columns.component.html @@ -104,7 +104,7 @@
{{getTitle()}}
- +
Foreign key
@@ -136,7 +136,7 @@
Foreign key

Source
-
+
@@ -158,7 +158,7 @@
Source

Underlying Tables
-
Unique name
+
diff --git a/src/app/views/schema-editing/edit-source-columns/edit-source-columns.component.ts b/src/app/views/schema-editing/edit-source-columns/edit-source-columns.component.ts index 3683e17f..78a18cf8 100644 --- a/src/app/views/schema-editing/edit-source-columns/edit-source-columns.component.ts +++ b/src/app/views/schema-editing/edit-source-columns/edit-source-columns.component.ts @@ -1,7 +1,7 @@ import {Component, computed, Injector, Input, OnDestroy, OnInit, Signal, signal, WritableSignal} from '@angular/core'; import {RelationalResult, UiColumnDefinition} from '../../../components/data-view/models/result-set.model'; import {CrudService} from '../../../services/crud.service'; -import {ColumnRequest, EditTableRequest} from '../../../models/ui-request.model'; +import {ColumnRequest} from '../../../models/ui-request.model'; import {ActivatedRoute} from '@angular/router'; import * as $ from 'jquery'; import {ToasterService} from '../../../components/toast-exposer/toaster.service'; @@ -14,6 +14,7 @@ import { AllocationPartitionModel, AllocationPlacementModel, EntityType, + ForeignKeyModel, NamespaceModel, TableModel } from '../../../models/catalog.model'; @@ -36,32 +37,21 @@ export class EditSourceColumnsComponent implements OnInit, OnDestroy { ) { this.foreignKeys = computed(() => { + const catalog = this._catalog.listener(); const namespace = this.namespace(); const entity = this.entity(); if (!namespace || !entity) { return this.foreignKeys(); } - const uml = this._crud.getUml(new EditTableRequest(namespace.id)); - if (!uml) { - return []; - } - const fks = new Map(); - uml.subscribe(res => res.foreignKeys.forEach((v, k) => { - if ((v.sourceSchema + '.' + v.sourceTable) === this._catalog.getFullEntityName(entity.id)) { - if (fks.has(v.fkName)) { - const fk = fks.get(v.fkName); - fk.targetColumn = fk.targetColumn + ', ' + v.targetColumn; - fk.sourceColumn = fk.sourceColumn + ', ' + v.sourceColumn; - } else { - fks.set(v.fkName, v); - } - return [...fks.values()]; - } - }) - ); + const fks = new Map(); + _catalog.getKeys(entity.id).filter(k => !k.isPrimary).map(k => k).forEach(k => { + fks.set(catalog.getConstraintName(k.id), k); + return [...fks.values()]; + }); }); + this.columns = computed(() => { const catalog = this._catalog.listener(); if (!this.entity) { @@ -100,10 +90,11 @@ export class EditSourceColumnsComponent implements OnInit, OnDestroy { readonly addableStores: Signal; readonly columns: Signal; + readonly foreignKeys: Signal; errorMsg: string; editingCol: string; subscriptions = new Subscription(); - foreignKeys: Signal; + underlyingTables: WritableSignal<{}> = signal(null); public readonly EntityType = EntityType; @@ -211,6 +202,6 @@ export class EditSourceColumnsComponent implements OnInit, OnDestroy { } getAdapters(): Signal { - return computed(() => this.placements()?.map(a => this._catalog.getAdapter(a.adapterId))); + return computed(() => this.placements()?.map(a => this._catalog.getAdapter(a.adapterId)).filter(a => a)); } } diff --git a/src/app/views/schema-editing/edit-tables/edit-tables.component.ts b/src/app/views/schema-editing/edit-tables/edit-tables.component.ts index 4fb4684f..b5c658db 100644 --- a/src/app/views/schema-editing/edit-tables/edit-tables.component.ts +++ b/src/app/views/schema-editing/edit-tables/edit-tables.component.ts @@ -1,16 +1,14 @@ import { - Component, computed, - effect, + Component, + computed, ElementRef, Input, OnDestroy, OnInit, QueryList, Renderer2, - signal, - Signal, untracked, - ViewChildren, - WritableSignal + Signal, + ViewChildren } from '@angular/core'; import {CrudService} from '../../../services/crud.service'; import {EditTableRequest} from '../../../models/ui-request.model'; @@ -30,7 +28,14 @@ import {Subscription} from 'rxjs'; import {DbTable} from '../../uml/uml.model'; import {BreadcrumbService} from '../../../components/breadcrumb/breadcrumb.service'; import {CatalogService} from '../../../services/catalog.service'; -import {AllocationEntityModel, AllocationPartitionModel, AllocationPlacementModel, EntityType, NamespaceModel, TableModel} from '../../../models/catalog.model'; +import { + AllocationEntityModel, + AllocationPartitionModel, + AllocationPlacementModel, + EntityType, + NamespaceModel, + TableModel +} from '../../../models/catalog.model'; import {AdapterModel} from '../../adapters/adapter.model'; const INITIAL_TYPE = 'BIGINT'; @@ -361,7 +366,6 @@ export class EditTablesComponent implements OnInit, OnDestroy { } ); } - } export class Table {
Table