Skip to content

Derive sqlx::Type should support other attribute for enum variants #4391

Description

@michaelvanstraten

I have found these related issues/pull requests

Description

Deploying a new enum variant to the database is currently a breaking change for every service instance still running the old binary.

Take this definition:

#[derive(sqlx::Type)]
#[sqlx(type_name = "permission_enum")]
enum Permission {
    Add,
    Subtract,
}

Suppose we now add a Multiply permission. Since multiplication can be expressed as repeated addition, we grant Multiply to every user who already has Add and Subtract, and add the value to the database enum. The moment those rows exist, every instance still running the previous version fails when loading user permissions, because the derived Decode impl ends in:

invalid value "Multiply" for enum Permission

This makes rolling deployments impossible without a coordinated upgrade.

Prefered solution

Add a variant-level #[sqlx(other)] attribute, similar to serde's, marking the variant that unrecognized database values decode into. The variant captures the original value so it isn't lost:

#[derive(sqlx::Type)]
#[sqlx(type_name = "permission_enum")]
enum Permission {
    Add,
    Subtract,
    #[sqlx(other)]
    Unknown(String),
}

In the derived Decode impl would then replace the error arm with the unknown variant. For #[repr(..)] enums the payload would be the repr type (e.g. Unknown(i32)).

Open questions

#[derive(sqlx::Type)] also generates Encode, and encoding Unknown(s) back as s round-trips correctly and seems like the least surprising behavior, but it does let a hand-constructed Unknown("nonsense") reach the database. The alternative is to return an encode error when encountering Unknown.

Should a bare unit #[sqlx(other)] Unknown be allowed? It's convenient, but it has no meaningful encoding and discards information, so requiring the payload may be better.

Is this a breaking change? Why or why not?

No, the new attribute is optional, and existing Unknown variants will be untouched.

I'm happy to implement this unless someone beats me to it. I'm opening the issue first to gather feedback on the open questions above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions