vapor / fluent

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

Support for bitwise filtering #776

Closed jcbriones closed 1 month ago

jcbriones commented 1 month ago

Is your feature request related to a problem? Please describe. Yes, currently the only way to filter bitwise operations on a model's field is to use a custom query. Each SQL database supports comparing bitwise operations on an Int type, so it would be nice to have this functionality built-in.

Describe the solution you'd like The ability to perform bitwise operations directly in a Fluent query, such as:

// AND operation
Model.query(on: db).filter(\.$field & value)

// OR operation
Model.query(on: db).filter(\.$field | value)

// XOR operation
Model.query(on: db).filter(\.$field ^ value)

Where field conforms to OptionSet with a rawValue of BinaryInteger, and the value being compared is of the same type as the field.

Example Code:

struct Permission: OptionSet {
    let rawValue: Int

    static let add = Permission(rawValue: 1 << 0)
    static let update = Permission(rawValue: 1 << 1)
    static let delete = Permission(rawValue: 1 << 2)
}

final class User: Model {
    static let schema = "users"

    @ID(key: .id)
    var id: UUID?

    @Field(key: "permission")
    var permission: Permission

    init() {}

    init(id: UUID? = nil, permission: Permission) {
        self.id = id
        self.permission = permission
    }
}

Describe alternatives you've considered Currently, I have created an extension of QueryBuilder, but it would be nice if this functionality were supported natively in the Fluent module.

Additional context This feature would simplify the process of querying models based on bitwise operations, improving code readability and maintainability.

gwynne commented 1 month ago

You can accomplish this now with:

Model.query(on: db)
    .filter(\.$field, .custom("&"), value)

etc.

You can make it prettier with something like:

extension Fluent.DatabaseQuery.Filter.Method {
    public static var bitwiseAnd: Self { .custom("&") }
    public static var bitwiseOr: Self { .custom("|") }
    public static var bitwiseXor: Self { .custom("^") }
}

Which then allows you to write the filter as:

Model.query(on: db)
    .filter(\.$field, .bitwiseAnd, value)

You can then further extend it by providing your own operator overloads:

public func & <Model, Field>(lhs: KeyPath<Model, Field>, rhs: Field.Value) -> ModelValueFilter<Model>
    where Model: Fields, Field: QueryableProperty, Field.Value: OptionSet
{ lhs & Field.queryValue(rhs) }

public func & <Model, Field>(lhs: KeyPath<Model, Field>, rhs: Field.Value) -> ModelValueFilter<Model>
    where Model: Fields, Field: QueryableProperty, Field.Value: BinaryInteger
{ lhs & Field.queryValue(rhs) }

public func & <Model, Field>(lhs: KeyPath<Model, Field>, rhs: DatabaseQuery.Value) -> ModelValueFilter<Model>
    where Model: Fields, Field: QueryableProperty, Field.Value: OptionSet
{ .init(lhs, .bitwiseAnd, rhs) }

public func & <Model, Field>(lhs: KeyPath<Model, Field>, rhs: DatabaseQuery.Value) -> ModelValueFilter<Model>
    where Model: Fields, Field: QueryableProperty, Field.Value: BinaryInteger
{ .init(lhs, .bitwiseAnd, rhs) }

(and so on for the | and ^ operators)

At which point you should be able to use the "native" syntax:

Model.query(on: db)
    .filter(\.$field & value)

Unfortunately, I can't claim to have any plan to support this directly in Fluent, for three reasons:

  1. It's a very niche use case,
  2. For use cases of this sort I generally recommend using SQLKit, and
  3. Fluent 4 is effectively in "maintenance-only" mode at this point, although I haven't yet officially announced as such; new development effort is focused on Fluent 5.
jcbriones commented 1 month ago

Thanks I didn't know those were public for extensions to work. I thought they were internal to module only. I would give this a try later. Thanks!