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 }