diff --git a/ui/src/app/components/workspace/workspace.component.ts b/ui/src/app/components/workspace/workspace.component.ts index 9e5d15a4f..a5862562d 100644 --- a/ui/src/app/components/workspace/workspace.component.ts +++ b/ui/src/app/components/workspace/workspace.component.ts @@ -45,7 +45,7 @@ export class WorkspaceComponent implements OnInit, OnDestroy { converObj!: Subscription ddlsumconvObj!: Subscription ddlObj!: Subscription - rerenderObj!: Subscription; + rerenderObj!: Subscription isLeftColumnCollapse: boolean = false isRightColumnCollapse: boolean = true isMiddleColumnCollapse: boolean = true @@ -70,7 +70,7 @@ export class WorkspaceComponent implements OnInit, OnDestroy { private sidenav: SidenavService, private router: Router, private clickEvent: ClickEventService, - private fetch: FetchService, + private fetch: FetchService ) { this.currentObject = null } @@ -95,15 +95,15 @@ export class WorkspaceComponent implements OnInit, OnDestroy { this.isMiddleColumnCollapse = !flag }) - if (this.data.autoGenMap){ + if (this.data.autoGenMap) { this.autoGenMapObj = this.data.autoGenMap.subscribe((autoGenMap) => { this.autoGenMap = autoGenMap }) } - if (this.data.treeUpdate){ + if (this.data.treeUpdate) { this.rerenderObj = this.data.treeUpdate.subscribe(() => { - this.reRenderObjectExplorerSpanner(); - }); + this.reRenderObjectExplorerSpanner() + }) } this.convObj = this.data.conv.subscribe((data: IConv) => { @@ -132,24 +132,18 @@ export class WorkspaceComponent implements OnInit, OnDestroy { this.objectExplorerInitiallyRender = true } - if (this.currentObject && this.currentObject.type === ObjectExplorerNodeType.Table) { - this.ccData = this.currentObject - ? this.conversion.getCheckConstrainst(this.currentObject.id, data) - : [] + ? this.conversion.getCheckConstrainst(this.currentObject.id, data) + : [] this.fkData = this.currentObject ? this.conversion.getFkMapping(this.currentObject.id, data) : [] - this.tableData = this.currentObject ? this.conversion.getColumnMapping(this.currentObject.id, data) : [] - - - } if ( this.currentObject && @@ -162,7 +156,7 @@ export class WorkspaceComponent implements OnInit, OnDestroy { this.currentObject.id ) } - this.dialect = (this.conv.SpDialect === "postgresql") ? "PostgreSQL" : "Google Standard SQL" + this.dialect = this.conv.SpDialect === 'postgresql' ? 'PostgreSQL' : 'Google Standard SQL' }) this.converObj = this.data.conversionRate.subscribe((rates: any) => { @@ -190,11 +184,11 @@ export class WorkspaceComponent implements OnInit, OnDestroy { this.convObj.unsubscribe() this.ddlObj.unsubscribe() this.ddlsumconvObj.unsubscribe() - if (this.autoGenMapObj){ + if (this.autoGenMapObj) { this.autoGenMapObj.unsubscribe() } - if (this.rerenderObj){ - this.rerenderObj.unsubscribe(); + if (this.rerenderObj) { + this.rerenderObj.unsubscribe() } } @@ -228,22 +222,20 @@ export class WorkspaceComponent implements OnInit, OnDestroy { this.srcTree = this.conversion.createTreeNodeForSource(this.conv, this.conversionRates) } - changeCurrentObject(object: FlatNode) { if (object.type === ObjectExplorerNodeType.Table) { this.currentObject = object this.tableData = this.conversion.getColumnMapping(this.currentObject.id, this.conv) - this.ccData = this.conversion.getCheckConstrainst(this.currentObject.id, this.conv) + this.ccData = this.conversion.getCheckConstrainst(this.currentObject.id, this.conv) this.fkData = [] - this.fkData = this.conversion.getFkMapping(this.currentObject.id, this.conv) + this.fkData = this.conversion.getFkMapping(this.currentObject.id, this.conv) } else if (object.type === ObjectExplorerNodeType.Index) { this.currentObject = object this.indexData = this.conversion.getIndexMapping(object.parentId, this.conv, object.id) } else if (object.type === ObjectExplorerNodeType.Sequence) { this.currentObject = object this.sequenceData = this.conversion.getSequenceMapping(object.id, this.conv) - } - else { + } else { this.currentObject = null } } @@ -283,7 +275,7 @@ export class WorkspaceComponent implements OnInit, OnDestroy { let config: IDbConfig = JSON.parse(localStorage.getItem(StorageKeys.Config)!) connectionDetail = config?.hostName + ' : ' + config?.port } else { - connectionDetail = this.conv.DatabaseName + connectionDetail = this.conv.DatabaseName } let viewAssesmentData: IViewAssesmentData = { srcDbType: this.srcDbName, @@ -301,12 +293,15 @@ export class WorkspaceComponent implements OnInit, OnDestroy { downloadSession(this.conv) } - downloadArtifacts(){ + downloadArtifacts() { let zip = new JSZip() let fileNameHeader = `${this.conv.DatabaseName}` this.fetch.getDStructuredReport().subscribe({ next: (resStructured: IStructuredReport) => { - let resJson = JSON.stringify(resStructured).replace(/9223372036854776000/g, '9223372036854775807') + let resJson = JSON.stringify(resStructured).replace( + /9223372036854776000/g, + '9223372036854775807' + ) let fileName = fileNameHeader + '_migration_structuredReport.json' // add structured report to zip file zip.file(fileName, resJson) @@ -318,28 +313,30 @@ export class WorkspaceComponent implements OnInit, OnDestroy { next: (resDDL: string) => { // add spanner DDL to zip file zip.file(fileNameHeader + '_spannerDDL.txt', resDDL) - let resJsonSession = JSON.stringify(this.conv).replace(/9223372036854776000/g, '9223372036854775807') + let resJsonSession = JSON.stringify(this.conv).replace( + /9223372036854776000/g, + '9223372036854775807' + ) let sessionFileName = `${this.conv.SessionName}_${this.conv.DatabaseType}_${fileNameHeader}.json` // add session to zip file zip.file(sessionFileName, resJsonSession) // Generate the zip file asynchronously - zip.generateAsync({ type: 'blob' }) - .then((blob: Blob) => { - var a = document.createElement('a'); - a.href = URL.createObjectURL(blob); - a.download = `${fileNameHeader}_artifacts`; - a.click(); + zip.generateAsync({ type: 'blob' }).then((blob: Blob) => { + var a = document.createElement('a') + a.href = URL.createObjectURL(blob) + a.download = `${fileNameHeader}_artifacts` + a.click() }) - } + }, }) - } + }, }) - } + }, }) } // downloads structured report of the migration in JSON format - downloadStructuredReport(){ + downloadStructuredReport() { var a = document.createElement('a') this.fetch.getDStructuredReport().subscribe({ next: (res: IStructuredReport) => { @@ -347,33 +344,33 @@ export class WorkspaceComponent implements OnInit, OnDestroy { a.href = 'data:text/json;charset=utf-8,' + encodeURIComponent(resJson) a.download = `${this.conv.DatabaseName}_migration_structuredReport.json` a.click() - } + }, }) } // downloads text report of the migration in text format in more human readable form - downloadTextReport(){ + downloadTextReport() { var a = document.createElement('a') this.fetch.getDTextReport().subscribe({ next: (res: string) => { a.href = 'data:text;charset=utf-8,' + encodeURIComponent(res) a.download = `${this.conv.DatabaseName}_migration_textReport.txt` a.click() - } + }, }) } // downloads text file of Spanner's DDL of the schema. However this is optimized for reading and includes comments, foreign keys // and doesn't add backticks around table and column names. This is not strictly - // legal Cloud Spanner DDL (Cloud Spanner doesn't currently support comments). - downloadDDL(){ + // legal Cloud Spanner DDL (Cloud Spanner doesn't currently support comments). + downloadDDL() { var a = document.createElement('a') this.fetch.getDSpannerDDL().subscribe({ next: (res: string) => { a.href = 'data:text;charset=utf-8,' + encodeURIComponent(res) a.download = `${this.conv.DatabaseName}_spannerDDL.txt` a.click() - } + }, }) } @@ -413,27 +410,50 @@ export class WorkspaceComponent implements OnInit, OnDestroy { prepareMigration() { this.fetch.getTableWithErrors().subscribe({ next: (res: ITableIdAndName[]) => { - if (res != null && res.length !=0) - { - let errMsg = 'Please fix the errors for the following tables to move ahead: '+ res.map(x => x.Name).join(', ') + if (res != null && res.length != 0) { + let errMsg = + 'Please fix the errors for the following tables to move ahead: ' + + res.map((x) => x.Name).join(', ') this.dialog.open(InfodialogComponent, { data: { message: errMsg, type: 'error', title: 'Error in Spanner Draft' }, maxWidth: '500px', }) } else if (this.isOfflineStatus) { this.dialog.open(InfodialogComponent, { - data: { message: "Please configure spanner project id and instance id to proceed", type: 'error', title: 'Configure Spanner' }, + data: { + message: 'Please configure spanner project id and instance id to proceed', + type: 'error', + title: 'Configure Spanner', + }, maxWidth: '500px', }) } else if (Object.keys(this.conv.SpSchema).length == 0) { this.dialog.open(InfodialogComponent, { - data: { message: "Please restore some table(s) to proceed with the migration", type: 'error', title: 'All tables skipped' }, + data: { + message: 'Please restore some table(s) to proceed with the migration', + type: 'error', + title: 'All tables skipped', + }, maxWidth: '500px', }) } else { - this.router.navigate(['/prepare-migration']) + this.fetch.validateCheckConstraint().subscribe((res: any) => { + if (res) { + this.router.navigate(['/prepare-migration']) + } else { + this.dialog.open(InfodialogComponent, { + data: { + message: + 'Type Mismatch Error. Check the dependencies of type in check constraints tab', + type: 'warning', + title: 'Type Mismatch Error', + }, + maxWidth: '500px', + }) + } + }) } - } + }, }) } spannerTab() { diff --git a/ui/src/app/services/conversion/conversion.service.ts b/ui/src/app/services/conversion/conversion.service.ts index 60e8c219a..b80679876 100644 --- a/ui/src/app/services/conversion/conversion.service.ts +++ b/ui/src/app/services/conversion/conversion.service.ts @@ -297,8 +297,7 @@ export class ConversionService { ] } getCheckConstrainst(tableId: string, data: IConv): ICcTabData[] { - debugger - let srcArr = data.SrcSchema[tableId].CheckConstraints + let srcArr = data.SrcSchema[tableId].CheckConstraints || [] let spArr = data.SpSchema[tableId].CheckConstraint || [] let res: ICcTabData[] = [] diff --git a/ui/src/app/services/fetch/fetch.service.ts b/ui/src/app/services/fetch/fetch.service.ts index 5bf5ccdbd..c86103319 100644 --- a/ui/src/app/services/fetch/fetch.service.ts +++ b/ui/src/app/services/fetch/fetch.service.ts @@ -205,6 +205,10 @@ export class FetchService { ) } + validateCheckConstraint() { + return this.http.get(`${this.url}/validateCheckConstraint`) + } + updateTable(tableName: string, data: IUpdateTable): any { return this.http.post>(`${this.url}/typemap/table?table=${tableName}`, data) } diff --git a/webv2/api/schema.go b/webv2/api/schema.go index ceae6850b..5eff1522a 100644 --- a/webv2/api/schema.go +++ b/webv2/api/schema.go @@ -526,6 +526,45 @@ func UpdateCheckConstraint(w http.ResponseWriter, r *http.Request) { } +// validate the type of column when there is check constraints +func ValidateCheckConstraint(w http.ResponseWriter, r *http.Request) { + sessionState := session.GetSessionState() + if sessionState.Conv == nil || sessionState.Driver == "" { + http.Error(w, fmt.Sprintf("Schema is not converted or Driver is not configured properly. Please retry converting the database to Spanner."), http.StatusNotFound) + return + } + sessionState.Conv.ConvLock.Lock() + defer sessionState.Conv.ConvLock.Unlock() + + sp := sessionState.Conv.SpSchema + src := sessionState.Conv.SrcSchema + + flag := true + + for _, step1 := range src { + + for _, step := range sp[step1.Id].ColDefs { + + if len(sp[step1.Id].CheckConstraint) != 0 && len(src[step1.Id].CheckConstraints) != 0 { + spType := step.T.Name + srcType := src[step1.Id].ColDefs[step.Id].Type + + actualType := mysqlDefaultTypeMap[srcType.Name] + + if actualType.Name != spType { + flag = false + break + } + } + + } + + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(flag) +} + // renameForeignKeys checks the new names for spanner name validity, ensures the new names are already not used by existing tables // secondary indexes or foreign key constraints. If above checks passed then foreignKey renaming reflected in the schema else appropriate // error thrown. diff --git a/webv2/config.json b/webv2/config.json index e2e1684f5..3d0ccb95a 100644 --- a/webv2/config.json +++ b/webv2/config.json @@ -1,5 +1,5 @@ { - "GCPProjectID": "vivekyadav-1731900261", - "SpannerProjectID": "vivekyadav-1731900261", - "SpannerInstanceID": "spanner-demo" + "GCPProjectID": "", + "SpannerProjectID": "", + "SpannerInstanceID": "" } diff --git a/webv2/routes.go b/webv2/routes.go index 70fda7e6d..bcb02d980 100644 --- a/webv2/routes.go +++ b/webv2/routes.go @@ -76,6 +76,7 @@ func getRoutes() *mux.Router { router.HandleFunc("/spannerDefaultTypeMap", api.SpannerDefaultTypeMap).Methods("GET") router.HandleFunc("/autoGenMap", api.GetAutoGenMap).Methods("GET") router.HandleFunc("/getSequenceKind", api.GetSequenceKind).Methods("GET") + router.HandleFunc("/validateCheckConstraint", api.ValidateCheckConstraint).Methods("GET") router.HandleFunc("/setparent", api.SetParentTable).Methods("GET") router.HandleFunc("/removeParent", api.RemoveParentTable).Methods("POST")