github.com/bishtawi/migrate/v4@v4.8.11/internal/cli/commands.go (about)

     1  package cli
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"github.com/bishtawi/migrate/v4"
     7  	_ "github.com/bishtawi/migrate/v4/database/stub" // TODO remove again
     8  	_ "github.com/bishtawi/migrate/v4/source/file"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  )
    15  
    16  func nextSeq(matches []string, dir string, seqDigits int) (string, error) {
    17  	if seqDigits <= 0 {
    18  		return "", errors.New("Digits must be positive")
    19  	}
    20  
    21  	nextSeq := 1
    22  	if len(matches) > 0 {
    23  		filename := matches[len(matches)-1]
    24  		matchSeqStr := strings.TrimPrefix(filename, dir)
    25  		idx := strings.Index(matchSeqStr, "_")
    26  		if idx < 1 { // Using 1 instead of 0 since there should be at least 1 digit
    27  			return "", errors.New("Malformed migration filename: " + filename)
    28  		}
    29  		matchSeqStr = matchSeqStr[0:idx]
    30  		var err error
    31  		nextSeq, err = strconv.Atoi(matchSeqStr)
    32  		if err != nil {
    33  			return "", err
    34  		}
    35  		nextSeq++
    36  	}
    37  	if nextSeq <= 0 {
    38  		return "", errors.New("Next sequence number must be positive")
    39  	}
    40  
    41  	nextSeqStr := strconv.Itoa(nextSeq)
    42  	if len(nextSeqStr) > seqDigits {
    43  		return "", fmt.Errorf("Next sequence number %s too large. At most %d digits are allowed", nextSeqStr, seqDigits)
    44  	}
    45  	padding := seqDigits - len(nextSeqStr)
    46  	if padding > 0 {
    47  		nextSeqStr = strings.Repeat("0", padding) + nextSeqStr
    48  	}
    49  	return nextSeqStr, nil
    50  }
    51  
    52  // cleanDir normalizes the provided directory
    53  func cleanDir(dir string) string {
    54  	dir = filepath.Clean(dir)
    55  	switch dir {
    56  	case ".":
    57  		return ""
    58  	case "/":
    59  		return dir
    60  	default:
    61  		return dir + "/"
    62  	}
    63  }
    64  
    65  // createCmd (meant to be called via a CLI command) creates a new migration
    66  func createCmd(dir string, startTime time.Time, format string, name string, ext string, seq bool, seqDigits int) {
    67  	dir = cleanDir(dir)
    68  	var base string
    69  	if seq && format != defaultTimeFormat {
    70  		log.fatalErr(errors.New("The seq and format options are mutually exclusive"))
    71  	}
    72  	if seq {
    73  		if seqDigits <= 0 {
    74  			log.fatalErr(errors.New("Digits must be positive"))
    75  		}
    76  		matches, err := filepath.Glob(dir + "*" + ext)
    77  		if err != nil {
    78  			log.fatalErr(err)
    79  		}
    80  		nextSeqStr, err := nextSeq(matches, dir, seqDigits)
    81  		if err != nil {
    82  			log.fatalErr(err)
    83  		}
    84  		base = fmt.Sprintf("%v%v_%v.", dir, nextSeqStr, name)
    85  	} else {
    86  		switch format {
    87  		case "":
    88  			log.fatal("Time format may not be empty")
    89  		case "unix":
    90  			base = fmt.Sprintf("%v%v_%v.", dir, startTime.Unix(), name)
    91  		case "unixNano":
    92  			base = fmt.Sprintf("%v%v_%v.", dir, startTime.UnixNano(), name)
    93  		default:
    94  			base = fmt.Sprintf("%v%v_%v.", dir, startTime.Format(format), name)
    95  		}
    96  	}
    97  
    98  	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
    99  		log.fatalErr(err)
   100  	}
   101  
   102  	createFile(base + "up" + ext)
   103  	createFile(base + "down" + ext)
   104  }
   105  
   106  func createFile(fname string) {
   107  	if _, err := os.Create(fname); err != nil {
   108  		log.fatalErr(err)
   109  	}
   110  }
   111  
   112  func gotoCmd(m *migrate.Migrate, v uint) {
   113  	if err := m.Migrate(v); err != nil {
   114  		if err != migrate.ErrNoChange {
   115  			log.fatalErr(err)
   116  		} else {
   117  			log.Println(err)
   118  		}
   119  	}
   120  }
   121  
   122  func upCmd(m *migrate.Migrate, limit int) {
   123  	if limit >= 0 {
   124  		if err := m.Steps(limit); err != nil {
   125  			if err != migrate.ErrNoChange {
   126  				log.fatalErr(err)
   127  			} else {
   128  				log.Println(err)
   129  			}
   130  		}
   131  	} else {
   132  		if err := m.Up(); err != nil {
   133  			if err != migrate.ErrNoChange {
   134  				log.fatalErr(err)
   135  			} else {
   136  				log.Println(err)
   137  			}
   138  		}
   139  	}
   140  }
   141  
   142  func downCmd(m *migrate.Migrate, limit int) {
   143  	if limit >= 0 {
   144  		if err := m.Steps(-limit); err != nil {
   145  			if err != migrate.ErrNoChange {
   146  				log.fatalErr(err)
   147  			} else {
   148  				log.Println(err)
   149  			}
   150  		}
   151  	} else {
   152  		if err := m.Down(); err != nil {
   153  			if err != migrate.ErrNoChange {
   154  				log.fatalErr(err)
   155  			} else {
   156  				log.Println(err)
   157  			}
   158  		}
   159  	}
   160  }
   161  
   162  func dropCmd(m *migrate.Migrate) {
   163  	if err := m.Drop(); err != nil {
   164  		log.fatalErr(err)
   165  	}
   166  }
   167  
   168  func forceCmd(m *migrate.Migrate, v int) {
   169  	if err := m.Force(v); err != nil {
   170  		log.fatalErr(err)
   171  	}
   172  }
   173  
   174  func versionCmd(m *migrate.Migrate) {
   175  	v, dirty, err := m.Version()
   176  	if err != nil {
   177  		log.fatalErr(err)
   178  	}
   179  	if dirty {
   180  		log.Printf("%v (dirty)\n", v)
   181  	} else {
   182  		log.Println(v)
   183  	}
   184  }
   185  
   186  // numDownMigrationsFromArgs returns an int for number of migrations to apply
   187  // and a bool indicating if we need a confirm before applying
   188  func numDownMigrationsFromArgs(applyAll bool, args []string) (int, bool, error) {
   189  	if applyAll {
   190  		if len(args) > 0 {
   191  			return 0, false, errors.New("-all cannot be used with other arguments")
   192  		}
   193  		return -1, false, nil
   194  	}
   195  
   196  	switch len(args) {
   197  	case 0:
   198  		return -1, true, nil
   199  	case 1:
   200  		downValue := args[0]
   201  		n, err := strconv.ParseUint(downValue, 10, 64)
   202  		if err != nil {
   203  			return 0, false, errors.New("can't read limit argument N")
   204  		}
   205  		return int(n), false, nil
   206  	default:
   207  		return 0, false, errors.New("too many arguments")
   208  	}
   209  }