0001 // MigrationManager.swift 0002 // 0003 // The MIT License (MIT) 0004 // 0005 // Copyright (c) 2015 Formbound 0006 // 0007 // Permission is hereby granted, free of charge, to any person obtaining a copy 0008 // of this software and associated documentation files (the "Software"), to deal 0009 // in the Software without restriction, including without limitation the rights 0010 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 0011 // copies of the Software, and to permit persons to whom the Software is 0012 // furnished to do so, subject to the following conditions: 0013 // 0014 // The above copyright notice and this permission notice shall be included in all 0015 // copies or substantial portions of the Software. 0016 // 0017 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 0018 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 0019 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 0020 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 0021 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 0022 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 0023 // SOFTWARE. 0024 0025 @_exported import File 0026 0027 public struct MigrationError: ErrorType { 0028 public let description: String 0029 } 0030 0031 public struct Migration
MigrationManager.swift:49 throw MigrationError(description: "up.sql not found at \(upPath)")MigrationManager.swift:87 throw MigrationError(description: "Unable to open find migrations directory at \(path)")MigrationManager.swift:95 throw MigrationError(MigrationManager.swift:121 throw MigrationError(description: "No migrations defined")MigrationManager.swift:125 throw MigrationError(description: "Target version out of range")MigrationManager.swift:154 throw MigrationError(MigrationManager.swift:165 throw MigrationError(MigrationManager.swift:179 throw MigrationError(MigrationManager.swift:185 throw MigrationError({ 0032 public let upStatement: String 0033 public let downStatement
MigrationManager.swift:70 private(set) public var migrationsByNumber: [Int: Migration] = [:]: String? 0034 0035 public init(path: String) throws { 0036 0037 var path = path 0038 0039 if path.characters.last == "/" { 0040 path = String(path.characters.dropLast()) 0041 } 0042 0043 let upPath = path + "/up.sql" 0044 let downPath = path + "/down.sql" 0045 0046 var (fileExists, isDirectory) = File.fileExistsAt(upPath) 0047 0048 guard fileExists && !isDirectory else { 0049 throw MigrationError(description: "up.sql not found at \(upPath)") 0050 } 0051 0052 0053 self.upStatement = try String(data: File(path: upPath).read()) 0054 0055 (fileExists, isDirectory) = File.fileExistsAt(downPath) 0056 0057 if fileExists && !isDirectory { 0058 self.downStatement = try String(data: File(path: downPath).read()) 0059 } else { 0060 self.downStatement = nil 0061 } 0062 } 0063 } 0064 0065 0066 public class MigrationManager<T
MigrationManager.swift:60 self.downStatement = nilMigrationManager.swift:164 guard let downStatement = migration.downStatement else {: Connection> { 0067 0068 public let connection
MigrationManager.swift:68 public let connection: TMigrationManager.swift:72 public init(migrationsDirectory path: String, connection: T) throws {: T 0069 0070 private(set) public var migrationsByNumber
MigrationManager.swift:74 self.connection = connectionMigrationManager.swift:137 try connection.transaction {: [Int: Migration] = [:] 0071 0072 public init(migrationsDirectory path: String, connection: T) throws { 0073 0074 self.connection = connection 0075 0076 try connection.execute("CREATE TABLE IF NOT EXISTS schema_migrations (timestamp TIMESTAMP(6) NOT NULL, from_version SMALLINT, to_version SMALLINT NOT NULL)") 0077 0078 var path = path 0079 0080 if path.characters.last == "/" { 0081 path = String(path.characters.dropLast()) 0082 } 0083 0084 let (fileExists, isDirectory) = File.fileExistsAt(path) 0085 0086 guard fileExists && isDirectory else { 0087 throw MigrationError(description: "Unable to open find migrations directory at \(path)") 0088 } 0089 0090 let directories = try File.contentsOfDirectoryAt(path).filter { path in 0091 path.split(".").last == "migration" 0092 }.sort() 0093 0094 guard !directories.isEmpty else { 0095 throw MigrationError( 0096 description: "No migrations found at \(path). Create folders named 'xx.migration' containing an 'up.sql' file, and optionally a 'down.sql' file \(path)" 0097 ) 0098 } 0099 0100 for (i, directoryPath) in directories.enumerate() { 0101 migrationsByNumber[i + 1] = try Migration(path: path + "/" + directoryPath) 0102 } 0103 0104 } 0105 0106 public var currentVersion
MigrationManager.swift:115 return migrationsByNumber.keys.sort().lastMigrationManager.swift:153 guard let migration = self.migrationsByNumber[migrationNumber] else {: Int? { 0107 guard let result = try? connection.execute("SELECT * FROM schema_migrations ORDER BY timestamp DESC LIMIT 1") else { 0108 return nil 0109 } 0110 0111 return (try? result.first?.value("to_version")) ?? nil 0112 } 0113 0114 public var latestVersion
MigrationManager.swift:128 let currentVersion = self.currentVersionMigrationManager.swift:178 guard let currentVersion = self.currentVersion else {: Int? { 0115 return migrationsByNumber.keys.sort().last 0116 } 0117 0118 public func migrate(to targetVersion: Int) throws { 0119 0120 guard let latestVersion = latestVersion else { 0121 throw MigrationError(description: "No migrations defined") 0122 } 0123 0124 guard targetVersion >= 0 && targetVersion <= latestVersion else { 0125 throw MigrationError(description: "Target version out of range") 0126 } 0127 0128 let currentVersion = self.currentVersion 0129 0130 var fromVersion = currentVersion ?? 0 0131 0132 if currentVersion == targetVersion { 0133 return 0134 } 0135 0136 while fromVersion != targetVersion { 0137 try connection.transaction { 0138 0139 // Are we migrating up or down? 0140 let upDirection = fromVersion < targetVersion 0141 0142 // The next predicted version 0143 let nextVersion = upDirection ? fromVersion + 1 : fromVersion - 1 0144 0145 /* 0146 The number of the migration we're either reading the up or down statement from 0147 0148 If the current version is *1*, and we're migrating up to *2*, we're executing the *up* statement of the migration with number *2* 0149 If the current version is *1*, and we're migrating down to 0 we're executing the *down* statement of the migration with number *1* 0150 */ 0151 let migrationNumber = upDirection ? nextVersion : nextVersion + 1 0152 0153 guard let migration = self.migrationsByNumber[migrationNumber] else { 0154 throw MigrationError( 0155 description: "Cannot migrate to version \(nextVersion). The migration with number \(migrationNumber) does not exist." 0156 ) 0157 } 0158 0159 if upDirection { 0160 0161 try self.connection.execute(Statement(migration.upStatement)) 0162 } 0163 else { 0164 guard let downStatement = migration.downStatement else { 0165 throw MigrationError( 0166 description: "Cannot migrate to version \(nextVersion). The migration with number \(migrationNumber) has no down statement." 0167 ) 0168 } 0169 0170 try self.connection.execute(Statement(downStatement)) 0171 } 0172 0173 try self.connection.execute( 0174 Statement("INSERT INTO schema_migrations (timestamp, from_version, to_version) VALUES(CURRENT_TIMESTAMP(6), $1, $2)", 0175 parameters: [fromVersion, nextVersion]) 0176 ) 0177 0178 guard let currentVersion = self.currentVersion else { 0179 throw MigrationError( 0180 description: "Failed to get current version of migration." 0181 ) 0182 } 0183 0184 guard nextVersion == currentVersion else { 0185 throw MigrationError( 0186 description: "The predicted next version(\(nextVersion)) does not match the current version (\(currentVersion)). Semething is wrong, possibly a bug!" 0187 ) 0188 } 0189 0190 fromVersion = currentVersion 0191 0192 } 0193 } 0194 } 0195 } 0196
MigrationManager.swift:120 guard let latestVersion = latestVersion else {