vapor / fluent-postgres-driver

🐘 PostgreSQL driver for Fluent.
MIT License
149 stars 53 forks source link

3.0-RC: Enums - No PostgreSQL column type known for <enum> #21

Closed 4np closed 6 years ago

4np commented 6 years ago

When I extend the User / Pet model to store PetType and UserType integer backed enums, PostgreSQL will fail on the UserType property on User:

screen shot 2018-02-28 at 10 37 10

Extended User / Pet model:

import Foundation
import FluentPostgreSQL
import Vapor

/// Pet

enum PetType: Int, Codable {
    case dog = 0
    case cat
    // It's pretty much my favorite animal. It's like a lion
    // and a tiger mixed... bred for its skills in magic.
    case liger
    case manBearPig
}

extension PetType: KeyStringDecodable {
    static var keyStringTrue: PetType {
        return .dog
    }

    static var keyStringFalse: PetType {
        return .cat
    }
}

struct Pet: PostgreSQLJSONType, Codable {
    var name: String
    var type: PetType
}

/// User

enum UserType: Int, Codable {
    case novice = 0
    case skilled
    case masterOfDisaster
}

extension UserType: KeyStringDecodable {
    static var keyStringTrue: UserType {
        return .novice
    }

    static var keyStringFalse: UserType {
        return .skilled
    }
}

final class User: PostgreSQLModel, Migration {
    static let idKey: WritableKeyPath<User, Int?> = \User.id
    var id: Int?
    var name: String
    var type: UserType = .novice
    var age: Int?
    var favoriteColors: [String]
    var pet: Pet

    init(id: Int? = nil, name: String, type: UserType, pet: Pet) {
        self.favoriteColors = []
        self.id = id
        self.name = name
        self.type = type
        self.pet = pet
    }
}

Changing the enum types from Integer backed to String backed does not matter:

enum UserType: String, Codable {
    case novice
    case skilled
    case masterOfDisaster
}

Note that PetType is not being used in the above code other than that it's part of the nested JSONB for Pet

tanner0101 commented 6 years ago

I added PostgreSQLEnumType to make this a bit easier (see the commit referenced above).

As long as the enum is RawRepresentable (backed by a string, int, etc) and the raw value is also a PostgreSQL-compatible type (all strings, integers, floats, bool, etc are) then implementation will be concise.

Here's what the models can look like:

enum PetType: Int, PostgreSQLEnumType {
    static let keyString: TupleMap = (.cat, .dog)
    case cat = 1
    case dog = 2
}
struct Pet: PostgreSQLModel, Migration {
    var id: Int?
    var type: PetType
    var name: String
}

Some example usage with expected return:

_ = try Pet(id: nil, type: .cat, name: "Ziz").save(on: conn).wait()
_ = try Pet(id: nil, type: .dog, name: "Spud").save(on: conn).wait()
let cats = try Pet.query(on: conn).filter(\.type == .cat).all().wait()
let dogs = try Pet.query(on: conn).filter(\.type == .dog).all().wait()
XCTAssertEqual(cats.count, 1)
XCTAssertEqual(cats.first?.name, "Ziz")
XCTAssertEqual(dogs.count, 1)
XCTAssertEqual(dogs.first?.name, "Spud")

Still trying to work out a way to make static let keyString: TupleMap not required, but for now we must have knowledge of two distinct values for each nested type for Fluent to work properly.

Let me know if that looks good. Thanks!