vapor / fluent-postgres-driver

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

Not-null constraint not working for types that encode and decode with a SingleValuedContainer #115

Closed olijzenga closed 5 years ago

olijzenga commented 5 years ago

I am having an issue with storing the following model in my database:

final class User {   
    var id: String?
    var name: CiString36?
}
extension User: PostgreSQLStringModel {}
extension User: Migration {}

Where CiString36 is defined as:

final class CiString36: Codable {
    var MAX_STRING_LENGTH: Int = 36
    var value: String

    init(_ string: String) throws {
        if string.count <= MAX_STRING_LENGTH {
            value = string
        } else {
            throw ModelError.SizeError("Got string of size \(string.count), expected <= \(MAX_STRING_LENGTH)")
        }
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let string = try container.decode(String.self)
        if string.count <= MAX_STRING_LENGTH {
            value = string
        } else {
            throw ModelError.SizeError("Got string of size \(string.count), expected <= \(MAX_STRING_LENGTH)")
        }
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(value)
    }
}

When I try to store a User with name set to nil in my database, I get the error:

"null value in column \"name\" violates not-null constraint"

When I look at the SQL queries that are executed when creating the table, I see that the NOT-NULL constraint is not properly handled:

CREATE TABLE "User" ("id" TEXT NOT NULL CONSTRAINT "pk:User.id" PRIMARY KEY, "name" TEXT NOT NULL)

It recognizes that CiString36 Encodes and Decodes to a string, and creates the column of type text. However, the name field from User is set to NOT NULL despite name being an optional variable.

Optional types seem to work fine in all other situations. Only types that encode and decode using a SingleValuedContainer have this issue.

Is this intended behaviour? And if so, is there a different way to do this?

Edit: Also if I remove the NOT NULL constraint from the name column manually using for example PGAdmin, everything works fine again until I recreate the table.

tanner0101 commented 5 years ago

@olijzenga for this, I'd recommend implementing the prepare method for the migration. The auto-migrations have very limited ability to infer things like this.

Check out the docs here: https://docs.vapor.codes/3.0/fluent/migrations/#custom-migrations

Lmk if you have any other questions, thanks :)