github.com/nokia/migrate/v4@v4.16.0/migration.go (about)

     1  package migrate
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"time"
     8  
     9  	"github.com/nokia/migrate/v4/source"
    10  )
    11  
    12  // DefaultBufferSize sets the in memory buffer size (in Bytes) for every
    13  // pre-read migration (see DefaultPrefetchMigrations).
    14  var DefaultBufferSize = uint(100000)
    15  
    16  // Migration holds information about a migration.
    17  // It is initially created from data coming from the source and then
    18  // used when run against the database.
    19  type Migration struct {
    20  	// Identifier can be any string to help identifying
    21  	// the migration in the source.
    22  	Identifier string
    23  
    24  	// Version is the version of this migration.
    25  	Version uint
    26  
    27  	// TargetVersion is the migration version after this migration
    28  	// has been applied to the database.
    29  	// Can be -1, implying that this is a NilVersion.
    30  	TargetVersion int
    31  
    32  	// Body holds an io.ReadCloser to the source.
    33  	Body io.ReadCloser
    34  
    35  	// BufferedBody holds an buffered io.Reader to the underlying Body.
    36  	BufferedBody io.Reader
    37  
    38  	// BufferSize defaults to DefaultBufferSize
    39  	BufferSize uint
    40  
    41  	// bufferWriter holds an io.WriteCloser and pipes to BufferBody.
    42  	// It's an *Closer for flow control.
    43  	bufferWriter io.WriteCloser
    44  
    45  	// Scheduled is the time when the migration was scheduled/ queued.
    46  	Scheduled time.Time
    47  
    48  	// StartedBuffering is the time when buffering of the migration source started.
    49  	StartedBuffering time.Time
    50  
    51  	// FinishedBuffering is the time when buffering of the migration source finished.
    52  	FinishedBuffering time.Time
    53  
    54  	// FinishedReading is the time when the migration source is fully read.
    55  	FinishedReading time.Time
    56  
    57  	// BytesRead holds the number of Bytes read from the migration source.
    58  	BytesRead int64
    59  
    60  	// Go Migration Function to be called.
    61  	MigrationFunc source.MigrationFunc
    62  
    63  	// marked migration as skipped.
    64  	Skipped bool
    65  }
    66  
    67  // NewMigration returns a new Migration and sets the body, identifier,
    68  // version and targetVersion. Body can be nil, which turns this migration
    69  // into a "NilMigration". If no identifier is provided, it will default to "<empty>".
    70  // targetVersion can be -1, implying it is a NilVersion.
    71  //
    72  // What is a NilMigration?
    73  // Usually each migration version coming from source is expected to have an
    74  // Up and Down migration. This is not a hard requirement though, leading to
    75  // a situation where only the Up or Down migration is present. So let's say
    76  // the user wants to migrate up to a version that doesn't have the actual Up
    77  // migration, in that case we still want to apply the version, but with an empty
    78  // body. We are calling that a NilMigration, a migration with an empty body.
    79  //
    80  // What is a NilVersion?
    81  // NilVersion is a const(-1). When running down migrations and we are at the
    82  // last down migration, there is no next down migration, the targetVersion should
    83  // be nil. Nil in this case is represented by -1 (because type int).
    84  func NewMigration(body io.ReadCloser, identifier string,
    85  	version uint, targetVersion int) (*Migration, error) {
    86  	tnow := time.Now()
    87  	m := &Migration{
    88  		Identifier:    identifier,
    89  		Version:       version,
    90  		TargetVersion: targetVersion,
    91  		Scheduled:     tnow,
    92  		Skipped:       false,
    93  	}
    94  
    95  	if body == nil {
    96  		if len(identifier) == 0 {
    97  			m.Identifier = "<empty>"
    98  		}
    99  
   100  		m.StartedBuffering = tnow
   101  		m.FinishedBuffering = tnow
   102  		m.FinishedReading = tnow
   103  		return m, nil
   104  	}
   105  
   106  	br, bw := io.Pipe()
   107  	m.Body = body // want to simulate low latency? newSlowReader(body)
   108  	m.BufferSize = DefaultBufferSize
   109  	m.BufferedBody = br
   110  	m.bufferWriter = bw
   111  	return m, nil
   112  }
   113  
   114  func NewFuncMigration(fn source.MigrationFunc, identifier string,
   115  	version uint, targetVersion int) *Migration {
   116  	tnow := time.Now()
   117  	m := &Migration{
   118  		Identifier:        identifier,
   119  		Version:           version,
   120  		TargetVersion:     targetVersion,
   121  		Scheduled:         tnow,
   122  		StartedBuffering:  tnow,
   123  		FinishedBuffering: tnow,
   124  		FinishedReading:   tnow,
   125  		MigrationFunc:     fn,
   126  		Skipped:           false,
   127  	}
   128  
   129  	return m
   130  }
   131  
   132  func NewSkippedMigration(identifier string,
   133  	version uint, targetVersion int) *Migration {
   134  	tnow := time.Now()
   135  	m := &Migration{
   136  		Identifier:        identifier,
   137  		Version:           version,
   138  		TargetVersion:     targetVersion,
   139  		Scheduled:         tnow,
   140  		StartedBuffering:  tnow,
   141  		FinishedBuffering: tnow,
   142  		FinishedReading:   tnow,
   143  		Skipped:           true,
   144  	}
   145  
   146  	return m
   147  }
   148  
   149  // String implements string.Stringer and is used in tests.
   150  func (m *Migration) String() string {
   151  	return fmt.Sprintf("%v [%v=>%v]", m.Identifier, m.Version, m.TargetVersion)
   152  }
   153  
   154  // LogString returns a string describing this migration to humans.
   155  func (m *Migration) LogString() string {
   156  	directionStr := "u"
   157  	if m.TargetVersion < int(m.Version) {
   158  		directionStr = "d"
   159  	}
   160  	return fmt.Sprintf("%v/%v %v", m.Version, directionStr, m.Identifier)
   161  }
   162  
   163  // Buffer buffers Body up to BufferSize.
   164  // Calling this function blocks. Call with goroutine.
   165  func (m *Migration) Buffer() error {
   166  	if m.Body == nil {
   167  		return nil
   168  	}
   169  
   170  	m.StartedBuffering = time.Now()
   171  
   172  	b := bufio.NewReaderSize(m.Body, int(m.BufferSize))
   173  
   174  	// start reading from body, peek won't move the read pointer though
   175  	// poor man's solution?
   176  	if _, err := b.Peek(int(m.BufferSize)); err != nil && err != io.EOF {
   177  		return err
   178  	}
   179  
   180  	m.FinishedBuffering = time.Now()
   181  
   182  	// write to bufferWriter, this will block until
   183  	// something starts reading from m.Buffer
   184  	n, err := b.WriteTo(m.bufferWriter)
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	m.FinishedReading = time.Now()
   190  	m.BytesRead = n
   191  
   192  	// close bufferWriter so Buffer knows that there is no
   193  	// more data coming
   194  	if err := m.bufferWriter.Close(); err != nil {
   195  		return err
   196  	}
   197  
   198  	// it's safe to close the Body too
   199  	if err := m.Body.Close(); err != nil {
   200  		return err
   201  	}
   202  
   203  	return nil
   204  }