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  }