Skip to content

Instantly share code, notes, and snippets.

@MihaelIsaev
Last active January 16, 2020 01:34
Show Gist options
  • Save MihaelIsaev/567ac6deb0c090da5d3daf88cdd93e8e to your computer and use it in GitHub Desktop.
Save MihaelIsaev/567ac6deb0c090da5d3daf88cdd93e8e to your computer and use it in GitHub Desktop.
// 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