-
-
Save adirburke/431ddfd8dbf7e0a7517962e53e057f48 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Enum Protocol | |
public protocol PostgresEnum: RawRepresentable, Codable, CaseIterable, PostgresDataConvertible { | |
static var name: String { get } | |
} | |
extension PostgresEnum { | |
static var name: String { String(describing: self).lowercased() } | |
} | |
extension PostgresDataType { | |
public static let `enum` = PostgresDataType(82624) | |
} | |
extension PostgresEnum where RawValue == String { | |
public static var postgresDataType: PostgresDataType { .text } | |
public var postgresData: PostgresData? { | |
.init(type: .enum, | |
formatCode: .binary, | |
value: ByteBuffer(ByteBufferView(rawValue.utf8))) | |
} | |
public init?(postgresData: PostgresData) { | |
guard let byteBuffer = postgresData.value, | |
let bytes = byteBuffer.getBytes(at: byteBuffer.readerIndex, length: byteBuffer.readableBytes), | |
let str = String(bytes: bytes, encoding: .utf8), | |
let v = Self(rawValue: str) | |
else { return nil } | |
self = v | |
} | |
} | |
// Create Migration Protocol | |
protocol PostgresEnumCreateMigration: Migration { | |
associatedtype Enum: PostgresEnum | |
} | |
extension PostgresEnumCreateMigration where Enum.RawValue == String { | |
func prepare(on database: Database) -> EventLoopFuture<Void> { | |
guard let db = database as? PostgresDatabase else { return database.eventLoop.future() } | |
let values = Enum.allCases.map { "'\($0.rawValue)'" }.joined(separator: ", ") | |
return db.query("CREATE TYPE \"\(Enum.name)\" AS ENUM (\(values));").transform(to: ()) | |
} | |
func revert(on database: Database) -> EventLoopFuture<Void> { | |
guard let db = database as? PostgresDatabase else { return database.eventLoop.future() } | |
return db.query("DROP TYPE \"\(Enum.name)\";").transform(to: ()) | |
} | |
} | |
// New Value Migration Protocol | |
public protocol PostgresEnumNewValuable { | |
var enumValueName: String { get } | |
var enumValuePosition: PostgresEnumNewValue.Position { get } | |
} | |
public struct PostgresEnumNewValue: PostgresEnumNewValuable { | |
public enum Position { | |
case last | |
case before(String) | |
case after(String) | |
} | |
public var enumValueName: String | |
public var enumValuePosition: Position | |
public init (_ name: String) { | |
self.enumValueName = name | |
self.enumValuePosition = .last | |
} | |
public init (_ name: String, before v: String) { | |
self.enumValueName = name | |
self.enumValuePosition = .before(v) | |
} | |
public init (_ name: String, after v: String) { | |
self.enumValueName = name | |
self.enumValuePosition = .after(v) | |
} | |
} | |
extension String: PostgresEnumNewValuable { | |
public var enumValueName: String { self } | |
public var enumValuePosition: PostgresEnumNewValue.Position { .last } | |
public func before(_ value: String) -> PostgresEnumNewValue { | |
.init(self, before: value) | |
} | |
public func after(_ value: String) -> PostgresEnumNewValue { | |
.init(self, after: value) | |
} | |
} | |
protocol PostgresEnumNewValuesMigration: Migration { | |
associatedtype Enum: PostgresEnum | |
var newValues: [PostgresEnumNewValuable] { get } | |
} | |
extension PostgresEnumNewValuesMigration where Enum.RawValue == String { | |
func prepare(on database: Database) -> EventLoopFuture<Void> { | |
guard let db = database as? PostgresDatabase else { return database.eventLoop.future() } | |
return newValues.map { newValue in | |
switch newValue.enumValuePosition { | |
case .last: | |
return db.query("ALTER TYPE \"\(Enum.name)\" ADD VALUE '\(newValue.enumValueName)';").transform(to: ()) | |
case .before(let v): | |
return db.query("ALTER TYPE \"\(Enum.name)\" ADD VALUE '\(newValue.enumValueName)' BEFORE '\(v)';").transform(to: ()) | |
case .after(let v): | |
return db.query("ALTER TYPE \"\(Enum.name)\" ADD VALUE '\(newValue.enumValueName)' AFTER '\(v)';").transform(to: ()) | |
} | |
}.flatten(on: database.eventLoop) | |
} | |
func revert(on database: Database) -> EventLoopFuture<Void> { | |
database.eventLoop.future() | |
} | |
} | |
/// Example! | |
enum Status: String, PostgresEnum { | |
case one, two, three | |
} | |
// In your model declare it as .text | |
// example with iField: | |
@iField(key: "status", .string, .required) | |
var status: Status | |
// in configure.swift you could create and update your enum by adding migrations | |
// for enum creation | |
struct CreateStatus: PostgresEnumCreateMigration { | |
typealias Enum = Status | |
} | |
app.migrations.add(CreateStatus(), to: .psql) | |
// to add new values to enum | |
struct AddValuesToStatus: PostgresEnumNewValuesMigration { | |
typealias Enum = Status | |
var newValues: [PostgresEnumNewValuable] { ["four", "zero".before("one")] } | |
} | |
app.migrations.add(AddValuesToStatus(), to: .psql) | |
/// 💡Example: how to programmaticaly run migrations | |
app.migrations.add(CreateStatus(), to: .psql) | |
app.migrations.add(CreateTodo(), to: .psql) | |
try app.migrator.setupIfNeeded().wait() | |
try app.migrator.prepareBatch().wait() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment