vapor / fluent

Vapor ORM (queries, models, and relations) for NoSQL and SQL databases
https://docs.vapor.codes/4.0/fluent/overview/
MIT License
1.32k stars 172 forks source link

String-backed enum causing issues with PostgresSQL #694

Closed uniformity-matters closed 4 years ago

uniformity-matters commented 4 years ago

I get the following error message when trying to save a model that uses a string-backed enum with fluent and PostgresSQL.

[ ERROR ] column "nationality" is of type nationalities but expression is of type text (transformAssignedExpr) (PostgresNIO/Connection/PostgresConnection+Database.swift:56) [ ERROR ] server: column "nationality" is of type nationalities but expression is of type text (transformAssignedExpr) [request-id: 4DE51988-F6A4-4FB2-9B1B-B6452FBF95EE] (Vapor/Middleware/ErrorMiddleware.swift:42) Querying data that uses the enum works.

I'm using Vapor 4 and this is how I added fluent and the postgres-driver as dependencies:

.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-rc"),
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0")

I created the enum like this:

enum Nationality: String, Codable, CaseIterable {
    static let schema = "nationalities"

    case german
    case french
    case spanish
}

and added it to my model like this:

final class Artist: Model {
...
    @Field(key: "nationality")
    var nationality: Nationality
...
}

The same operation works fine when using SQLite.

0xTim commented 4 years ago

How did you create the enum in the migration? There are docs on it at https://docs.vapor.codes/4.0/fluent/schema/#enum

uniformity-matters commented 4 years ago

I based it on the documentation you linked to.

return database.enum(Nationality.schema)
    .case("german")
    .case("french")
    .case("spanish")
    .create()
    .flatMap { (nationality) -> EventLoopFuture<Void> in
        database.schema(Artist.schema)
            .id()
            ...
            .field("nationality", nationality, .required)
                .create()
    }

Checking the database I can see a table _fluent_enums where all the enums with all their cases I created show up.

uniformity-matters commented 4 years ago

I worked around the problem by saving strings to the database and then having a computed var to get the swift enum. I'm still interested in knowing what the problem is with my initial, cleaner approach.

jdanthinne commented 4 years ago

Had the same issue, and instead of using strings (my first easy fix as well I must admit), I just created a migration for the enum itself, then in the migration for the type using the enum, I just read the enum, based on the doc.

struct CreatePostType: Migration {
    func prepare(on database: Database) -> EventLoopFuture<Void> {
        return database.enum("post_types")
            .case("article")
            .case("tutorial")
            .case("code")
            .create()
            .transform(to: ())
    }

    func revert(on database: Database) -> EventLoopFuture<Void> {
        return database.enum("post_types").delete()
    }
}

struct CreatePost: Migration {
    func prepare(on database: Database) -> EventLoopFuture<Void> {
        return database.enum("post_types").read().flatMap { postTypes in
              return database.schema("posts")
                  .id()
                  .field("url", .string, .required)
                  .field("title", .string, .required)
                  .field("type", postTypes, .required)
                  .unique(on: "url", name: "no_duplicate_url")
                  .create()
            }
        }
    }

    func revert(on database: Database) -> EventLoopFuture<Void> {
        return database.schema("posts").delete()
    }
}
uniformity-matters commented 4 years ago

I just tried to first create the enum in one migration and then read it in another as suggested. Unfortunately to no avail. Same problem - same error. The migrations run fine, but once I try to insert data programmatically the SQL INSERT INTO command fails.

jdanthinne commented 4 years ago

Did it throw the same error? Can you show us how you write the data? And your model should use the @Enum wrapper instead of @Field.

uniformity-matters commented 4 years ago

And your model should use the @enum wrapper instead of @field. 🤦🏻‍♂️ That did the trick.

Thanks to both of you!