Skip to content

Commit dedefca

Browse files
authored
Followup to interleaving to any table -- added checks before changing interleaved primary keys / dropping interleaved tables (#1218)
1 parent 703af28 commit dedefca

31 files changed

Lines changed: 805 additions & 1719 deletions

ui/package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/src/app/app.module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import { GcsMetadataDetailsFormComponent } from './components/gcs-metadata-detai
4343
import { ConnectionProfileFormComponent } from './components/connection-profile-form/connection-profile-form.component'
4444
import { SourceDetailsFormComponent } from './components/source-details-form/source-details-form.component'
4545
import { SidenavReviewChangesComponent } from './components/sidenav-review-changes/sidenav-review-changes.component'
46-
import { TableColumnChangesPreviewComponent } from './components/table-column-changes-preview/table-column-changes-preview.component'
4746
import { EndMigrationComponent } from './components/end-migration/end-migration.component'
4847
import { DataflowFormComponent } from './components/dataflow-form/dataflow-form.component';
4948
import { EditColumnMaxLengthComponent } from './components/edit-column-max-length/edit-column-max-length.component';
@@ -89,7 +88,6 @@ import { NgSelectModule } from '@ng-select/ng-select';
8988
GcsMetadataDetailsFormComponent,
9089
ConnectionProfileFormComponent,
9190
SidenavReviewChangesComponent,
92-
TableColumnChangesPreviewComponent,
9391
EndMigrationComponent,
9492
SourceDetailsFormComponent,
9593
DataflowFormComponent,

ui/src/app/components/object-detail/object-detail.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ <h3 class="title">
762762
placeholder="Select Interleave Type"
763763
[clearable]="false"
764764
appendTo="body"
765-
[(ngModel)]="interleaveType">
765+
[(ngModel)]="interleaveType" (change)="interleaveType === 'IN' ? (onDeleteAction = '') : null">
766766
</ng-select>
767767

768768
<ng-select *ngIf="interleaveType === 'IN PARENT'"

ui/src/app/components/object-detail/object-detail.component.spec.ts

Lines changed: 187 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ describe('ObjectDetailComponent', () => {
2929
let dialogSpyObj: jasmine.SpyObj<MatDialog>;
3030
let rowData: IColumnTabData[]
3131

32-
beforeEach(async () => {
33-
dataServiceSpy = jasmine.createSpyObj('DataService', ['updateSequence', 'dropSequence', 'updateCheckConstraint', 'reviewTableUpdate', 'setInterleave']);
32+
beforeEach(async () => { dataServiceSpy = jasmine.createSpyObj('DataService', ['updateSequence', 'dropSequence', 'updateCheckConstraint', 'reviewTableUpdate', 'setInterleave', 'dropTable', 'getConversionRate']);
3433
dataServiceSpy.updateSequence.and.returnValue(of({}));
3534
dataServiceSpy.dropSequence.and.returnValue(of(''));
3635
dataServiceSpy.reviewTableUpdate.and.returnValue(of(''));
@@ -67,6 +66,9 @@ describe('ObjectDetailComponent', () => {
6766
dataServiceSpy.conv = of(mockIConv);
6867
dataServiceSpy.tableInterleaveStatus = of({});
6968
dataServiceSpy.updateCheckConstraint.and.returnValue(of(''))
69+
dataServiceSpy.dropTable.and.returnValue(of(''))
70+
dataServiceSpy.updatePk = jasmine.createSpy('updatePk').and.returnValue(of(''));
71+
dataServiceSpy.getConversionRate.and.returnValue()
7072
})
7173

7274
beforeEach(() => {
@@ -541,4 +543,187 @@ describe('ObjectDetailComponent', () => {
541543
});
542544
});
543545

546+
it('should drop table successfully', () => {
547+
const dialogRefSpyObj = jasmine.createSpyObj({ afterClosed: of(ObjectDetailNodeType.Table), close: null });
548+
dialogSpyObj.open.and.returnValue(dialogRefSpyObj);
549+
component.currentObject = { id: 't1', name: 'test_table' } as FlatNode;
550+
spyOn(component.updateSidebar, 'emit');
551+
spyOn(component, 'tableInterleaveWith').and.returnValue([]);
552+
553+
component.dropTable();
554+
555+
expect(dialogSpyObj.open).toHaveBeenCalledWith(DropObjectDetailDialogComponent, jasmine.any(Object));
556+
expect(dataServiceSpy.dropTable).toHaveBeenCalledWith('t1');
557+
expect(component.isObjectSelected).toBe(false);
558+
expect(dataServiceSpy.getConversionRate).toHaveBeenCalled();
559+
expect(component.updateSidebar.emit).toHaveBeenCalledWith(true);
560+
expect(component.currentObject).toBeNull();
561+
});
562+
563+
it('should not drop table if it is interleaved', () => {
564+
component.currentObject = { id: 't1', name: 'test_table' } as FlatNode;
565+
spyOn(component, 'tableInterleaveWith').and.returnValue(['t2']);
566+
component.conv.SpSchema['t2'] = { Name: 'table2' } as any;
567+
568+
component.dropTable();
569+
570+
expect(dialogSpyObj.open).toHaveBeenCalledWith(InfodialogComponent, {
571+
data: {
572+
message: `Cannot drop the table as it has interleaving with table2. Remove the interleaving first to continue.`,
573+
title: 'Error',
574+
type: 'error',
575+
},
576+
maxWidth: '500px',
577+
});
578+
expect(dataServiceSpy.dropTable).not.toHaveBeenCalled();
579+
});
580+
581+
describe('tableInterleaveWith', () => {
582+
583+
it('should return an empty array if the table is not interleaved with any other table', () => {
584+
component.conv.SpSchema = {
585+
't1': { ParentTable: { Id: '' } } as any,
586+
't2': { ParentTable: { Id: '' } } as any,
587+
};
588+
const result = component.tableInterleaveWith('t1');
589+
expect(result).toEqual([]);
590+
});
591+
592+
it('should return child table IDs if the given table is a parent', () => {
593+
component.conv.SpSchema = {
594+
't1': { ParentTable: { Id: '' } } as any,
595+
't2': { ParentTable: { Id: 't1' } } as any,
596+
't3': { ParentTable: { Id: 't1' } } as any,
597+
't4': { ParentTable: { Id: 't2' } } as any,
598+
};
599+
const result = component.tableInterleaveWith('t1');
600+
expect(result.sort()).toEqual(['t2', 't3'].sort());
601+
});
602+
603+
it('should return the parent table ID if the given table is a child', () => {
604+
component.conv.SpSchema = {
605+
't1': { ParentTable: { Id: '' } } as any,
606+
't2': { ParentTable: { Id: 't1' } } as any,
607+
};
608+
const result = component.tableInterleaveWith('t2');
609+
expect(result).toEqual(['t1']);
610+
});
611+
612+
it('should return both parent and child table IDs if the table is in the middle of an interleave chain', () => {
613+
component.conv.SpSchema = {
614+
't1': { ParentTable: { Id: '' } } as any,
615+
't2': { ParentTable: { Id: 't1' } } as any,
616+
't3': { ParentTable: { Id: 't2' } } as any,
617+
't4': { ParentTable: { Id: '' } } as any,
618+
};
619+
const result = component.tableInterleaveWith('t2');
620+
expect(result.sort()).toEqual(['t1', 't3'].sort());
621+
});
622+
});
623+
624+
describe('savePk', () => {
625+
let formBuilder: FormBuilder;
626+
627+
beforeEach(() => {
628+
formBuilder = new FormBuilder();
629+
component.currentObject = { id: 't1', name: 'test_table' } as FlatNode;
630+
component.conv.SpSchema['t1'] = {
631+
Id: 't1',
632+
Name: 'test_table',
633+
ColDefs: {
634+
'c1': { Name: 'col1' },
635+
'c2': { Name: 'col2' }
636+
},
637+
PrimaryKeys: [{ ColId: 'c1', Order: 1, Desc: false }]
638+
} as any;
639+
component.conv.SpSchema['t2'] = {
640+
Id: 't2',
641+
Name: 'table2',
642+
ColDefs: {
643+
'c3': { Name: 'col3' },
644+
'c4': { Name: 'col4' }
645+
},
646+
PrimaryKeys: [{ ColId: 'c3', Order: 1 }]
647+
} as any;
648+
});
649+
650+
it('should open error dialog if no PK columns are selected', () => {
651+
component.pkArray = formBuilder.array([]);
652+
component.pkData = [];
653+
component.savePk();
654+
655+
expect(dialogSpyObj.open).toHaveBeenCalledWith(InfodialogComponent, {
656+
data: { message: 'Add columns to the primary key for saving', type: 'error' },
657+
maxWidth: '500px',
658+
});
659+
expect(dataServiceSpy.updatePk).not.toHaveBeenCalled();
660+
});
661+
662+
it('should call updatePk when there are no interleaved tables', () => {
663+
component.pkData = [{ spId: 'c1', spIsPk: true, spOrder: 1 }] as IColumnTabData[];
664+
component.pkArray = formBuilder.array([
665+
formBuilder.group({ spColName: 'col1', spOrder: 1 })
666+
]);
667+
spyOn(component, 'tableInterleaveWith').and.returnValue([]);
668+
669+
component.savePk();
670+
671+
expect(dataServiceSpy.updatePk).toHaveBeenCalledWith({
672+
TableId: 't1',
673+
Columns: [{ ColId: 'c1', Desc: false, Order: 1 }]
674+
});
675+
});
676+
677+
it('should call updatePk for interleaved table if PK prefix is not modified', () => {
678+
component.pkData = [{ spId: 'c1', spIsPk: true, spOrder: 1 }] as IColumnTabData[];
679+
component.pkArray = formBuilder.array([
680+
formBuilder.group({ spColName: 'col1', spOrder: 1 })
681+
]);
682+
spyOn(component, 'tableInterleaveWith').and.returnValue(['t2']);
683+
spyOn(component, 'isPKPrefixModified').and.returnValue(false);
684+
685+
component.savePk();
686+
687+
expect(dataServiceSpy.updatePk).toHaveBeenCalled();
688+
expect(dialogSpyObj.open).not.toHaveBeenCalledWith(InfodialogComponent, jasmine.any(Object));
689+
});
690+
691+
it('should show error dialog if PK prefix is modified for an interleaved table', () => {
692+
component.pkData = [{ spId: 'c2', spIsPk: true, spOrder: 1 }] as IColumnTabData[];
693+
component.pkArray = formBuilder.array([
694+
formBuilder.group({ spColName: 'col2', spOrder: 1 })
695+
]);
696+
spyOn(component, 'tableInterleaveWith').and.returnValue(['t2']);
697+
spyOn(component, 'isPKPrefixModified').and.returnValue(true);
698+
699+
component.savePk();
700+
701+
expect(dialogSpyObj.open).toHaveBeenCalledWith(InfodialogComponent, {
702+
data: {
703+
message: 'Cannot update primary key as this primary key is part of interleaving with table(s) table2. Please remove the interleaved relationship and try again.',
704+
type: 'error',
705+
},
706+
maxWidth: '500px',
707+
});
708+
expect(dataServiceSpy.updatePk).not.toHaveBeenCalled();
709+
});
710+
711+
it('should show error dialog if updatePk fails', () => {
712+
const errorMessage = 'PK update failed';
713+
dataServiceSpy.updatePk.and.returnValue(of(errorMessage));
714+
component.pkData = [{ spId: 'c1', spIsPk: true, spOrder: 1 }] as IColumnTabData[];
715+
component.pkArray = formBuilder.array([
716+
formBuilder.group({ spColName: 'col1', spOrder: 1 })
717+
]);
718+
spyOn(component, 'tableInterleaveWith').and.returnValue([]);
719+
720+
component.savePk();
721+
722+
expect(dialogSpyObj.open).toHaveBeenCalledWith(InfodialogComponent, {
723+
data: { message: errorMessage, type: 'error' },
724+
maxWidth: '500px',
725+
});
726+
expect(component.isPkEditMode).toBe(true);
727+
});
728+
});
544729
});

0 commit comments

Comments
 (0)