github.com/octohelm/storage@v0.0.0-20240516030302-1ac2cc1ea347/pkg/migrator/diff.go (about) 1 package migrator 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/octohelm/storage/internal/sql/adapter" 9 "github.com/octohelm/storage/pkg/sqlbuilder" 10 ) 11 12 type action struct { 13 typ actionType 14 exprs []sqlbuilder.SqlExpr 15 } 16 17 type actions []action 18 19 func (a actions) Len() int { 20 return len(a) 21 } 22 23 func (a actions) Less(i, j int) bool { 24 return a[i].typ < a[j].typ 25 } 26 27 func (a actions) Swap(i, j int) { 28 a[i], a[j] = a[j], a[i] 29 } 30 31 func diff(dialect adapter.Dialect, currentTable sqlbuilder.Table, nextTable sqlbuilder.Table) (migrations actions) { 32 indexes := map[string]bool{} 33 34 migrate := func(typ actionType, name string, exprs ...sqlbuilder.SqlExpr) { 35 if len(exprs) > 0 { 36 37 switch typ { 38 case dropTableIndex, addTableIndex: 39 if _, ok := indexes[name]; ok { 40 return 41 } else { 42 indexes[name] = true 43 } 44 } 45 46 migrations = append(migrations, action{ 47 typ: typ, 48 exprs: exprs, 49 }) 50 } 51 } 52 53 // create nextTable 54 if currentTable == nil { 55 migrate(createTable, nextTable.TableName(), dialect.CreateTableIsNotExists(nextTable)...) 56 return 57 } 58 59 colChanges := map[string]actionType{} 60 61 // diff columns 62 nextTable.Cols().RangeCol(func(nextCol sqlbuilder.Column, idx int) bool { 63 if currentCol := currentTable.F(nextCol.Name()); currentCol != nil { 64 if nextCol != nil { 65 if deprecatedActions := nextCol.Def().DeprecatedActions; deprecatedActions != nil { 66 renameTo := deprecatedActions.RenameTo 67 if renameTo != "" { 68 prevCol := currentTable.F(renameTo) 69 if prevCol != nil { 70 colChanges[prevCol.Name()] = dropTableColumn 71 migrate(dropTableColumn, prevCol.Name(), dialect.DropColumn(prevCol)) 72 } 73 targetCol := nextTable.F(renameTo) 74 if targetCol == nil { 75 panic(fmt.Errorf("col `%s` is not declared", renameTo)) 76 } 77 migrate(renameTableColumn, nextCol.Name(), dialect.RenameColumn(nextCol, targetCol)) 78 currentTable.(sqlbuilder.ColumnCollectionManger).AddCol(targetCol) 79 return true 80 } 81 migrate(dropTableColumn, nextCol.Name(), dialect.DropColumn(nextCol)) 82 return true 83 } 84 85 prevColType := dialect.DataType(currentCol.Def()).Ex(context.Background()).Query() 86 currentColType := dialect.DataType(nextCol.Def()).Ex(context.Background()).Query() 87 88 if !strings.EqualFold(prevColType, currentColType) { 89 colChanges[nextCol.Name()] = modifyTableColumn 90 migrate(modifyTableColumn, nextCol.Name(), dialect.ModifyColumn(nextCol, currentCol)) 91 } 92 return true 93 } 94 95 colChanges[nextCol.Name()] = dropTableColumn 96 migrate(dropTableColumn, nextCol.Name(), dialect.DropColumn(nextCol)) 97 return true 98 } 99 100 if nextCol.Def().DeprecatedActions == nil { 101 migrate(addTableColumn, nextCol.Name(), dialect.AddColumn(nextCol)) 102 } 103 104 return true 105 }) 106 107 currentTable.Cols().RangeCol(func(col sqlbuilder.Column, idx int) bool { 108 // only drop tmp col 109 // when need to drop real data col, must declare deprecated for migrate 110 if strings.HasPrefix(col.Name(), "__") && nextTable.F(col.Name()) == nil { 111 // drop column 112 colChanges[col.Name()] = dropTableColumn 113 migrate(dropTableColumn, col.Name(), dialect.DropColumn(col)) 114 } 115 return true 116 }) 117 118 nextTable.Keys().RangeKey(func(key sqlbuilder.Key, idx int) bool { 119 name := key.Name() 120 if key.IsPrimary() { 121 // pkey could not change 122 return true 123 } 124 125 key.Columns().RangeCol(func(col sqlbuilder.Column, idx int) bool { 126 if tpe, ok := colChanges[col.Name()]; ok && tpe == modifyTableColumn { 127 // always re index when col type modified 128 migrate(dropTableIndex, key.Name(), dialect.DropIndex(key)) 129 migrate(addTableIndex, key.Name(), dialect.AddIndex(key)) 130 } 131 return true 132 }) 133 134 prevKey := currentTable.K(name) 135 if prevKey == nil { 136 migrate(addTableIndex, key.Name(), dialect.AddIndex(key)) 137 } else { 138 if !key.IsPrimary() { 139 indexDef := key.Columns().Ex(context.Background()).Query() 140 prevIndexDef := prevKey.Columns().Ex(context.Background()).Query() 141 142 if !strings.EqualFold(indexDef, prevIndexDef) { 143 migrate(dropTableIndex, key.Name(), dialect.DropIndex(key)) 144 migrate(addTableIndex, key.Name(), dialect.AddIndex(key)) 145 } 146 } 147 } 148 149 return true 150 }) 151 152 currentTable.Keys().RangeKey(func(key sqlbuilder.Key, idx int) bool { 153 colDropped := false 154 155 key.Columns().RangeCol(func(col sqlbuilder.Column, idx int) bool { 156 if tpe, ok := colChanges[col.Name()]; ok && tpe == dropTableColumn { 157 colDropped = true 158 return false 159 } 160 return true 161 }) 162 163 if colDropped { 164 // always drop related index when col drop 165 migrate(dropTableIndex, key.Name(), dialect.DropIndex(key)) 166 return true 167 } 168 169 if nextTable.K(key.Name()) == nil { 170 // drop index not exists 171 migrate(dropTableIndex, key.Name(), dialect.DropIndex(key)) 172 } 173 174 return true 175 }) 176 177 return 178 }