fluent-kit icon indicating copy to clipboard operation
fluent-kit copied to clipboard

Nested array/set filtering

Open valeriomazzeo opened this issue 5 years ago • 6 comments

Given the following model:

class MyModel: Model {
    static let schema = ""

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

    @OptionalField(key: "field")
    var field: Set<String>?

    required init() { }
}

Running this filter does not compile:

    let db: Database = { fatalError() }()
    let a = Set(["a"])
    MyModel.query(on: db).filter(\.$field ~~ a) // Does not work
    MyModel.query(on: db).filter(\.$field ~~ ["a"]) // Does not work
    MyModel.query(on: db).filter(\.$field, .subset(inverse: false), a) // It works
    MyModel.query(on: db).filter(\.$field, .subset(inverse: false), ["a"]) // It works

valeriomazzeo avatar May 29 '20 09:05 valeriomazzeo

I think here

The correct constraints are:

public func ~~ <Model, Field, Values>(lhs: KeyPath<Model, Field>, rhs: Values) -> ModelValueFilter<Model>
    where Model: FluentKit.Model,
        Field: QueryableProperty,
        Field.Value: OptionalType,
        Field.Value.Wrapped: Collection & Codable,
        Values: Collection,
        Values.Element == Field.Value.Wrapped.Element

instead of:

public func ~~ <Model, Field, Values>(lhs: KeyPath<Model, Field>, rhs: Values) -> ModelValueFilter<Model>
    where Model: FluentKit.Model,
        Field: FieldProtocol,
        Field.Value: OptionalType,
        Field.Value.Wrapped: Codable,
        Values: Collection,
        Values.Element == Field.Value.Wrapped
{

Same bug seem to apply to other operators as well.

valeriomazzeo avatar May 29 '20 10:05 valeriomazzeo

Sorry, deleted original comment as I'm realizing I misread and this has not been fixed.

Fluent doesn't yet support filtering on nested array and dictionary fields. I'm not sure exactly how this would work either since underlying driver support varies greatly.

For Postgres at least, you should be able to use the @> operator. Read more here: https://www.postgresql.org/docs/9.2/functions-array.html

Leaving this issue open as an enhancement to track nested array filtering.

tanner0101 avatar Jun 24 '20 23:06 tanner0101

Sorry, deleted original comment as I'm realizing I misread and this has not been fixed.

Fluent doesn't yet support filtering on nested array and dictionary fields. I'm not sure exactly how this would work either since underlying driver support varies greatly.

For Postgres at least, you should be able to use the @> operator. Read more here: https://www.postgresql.org/docs/9.2/functions-array.html

Leaving this issue open as an enhancement to track nested array filtering.

Why it could not be supported with custom operators ?

// Examaple
try await SomeModel
    .query(on: db)
    .filter(\.$stringsArrayField, .custom("&&"), "'{\"one\", \"two\"}'")
    .all()

ypotsiah avatar Aug 16 '23 07:08 ypotsiah

@ypotsiah in this instance he means custom Swift operators, so something like .filter(\.$stringsArrayField @> "'{\"one\", \"two\"}'"). You can do this by passing in your own custom operator as shown in your snippet

0xTim avatar Aug 16 '23 13:08 0xTim

@ypotsiah in this instance he means custom Swift operators, so something like .filter(\.$stringsArrayField @> "'{\"one\", \"two\"}'"). You can do this by passing in your own custom operator as shown in your snippet

@0xTim Unfortunately, my snippet is not compilable. Because existing implementation is:

    @discardableResult
    public func filter<Field>(
        _ field: KeyPath<Model, Field>,
        _ method: DatabaseQuery.Filter.Method,
        _ value: Field.Value
    ) -> Self
        where Field: QueryableProperty, Field.Model == Model
    {
        self.filter(.extendedPath(
            Model.path(for: field),
            schema: Model.schemaOrAlias,
            space: Model.spaceIfNotAliased
        ), method, Field.queryValue(value))
    }

To use custom values I have to implement something like that in my own QueryBuilder extension:

extension QueryBuilder {
    @discardableResult
    public func customFilter<Field>(
        _ field: KeyPath<Model, Field>,
        _ method: DatabaseQuery.Filter.Method,
        _ value: DatabaseQuery.Value
    ) -> Self
        where Field: QueryableProperty, Field.Model == Model
    {
        self.filter(.extendedPath(
            Model.path(for: field),
            schema: Model.schemaOrAlias,
            space: Model.alias == nil ? Model.space : nil
        ), method, value)
    }
}

So maybe better to have it "out of the box" in Fluent? If somebody would like to use custom part of query - it seems good addition instead of create full query with SQLKit after casting database to SQLDatabase.

ypotsiah avatar Aug 17 '23 12:08 ypotsiah

I'm going to defer to @gwynne here as this kind of change is probably out of scope for Fluent 4 (though there very well may a way to do this already, worst case SQLKit) but useful to add to the requests for Fluent 5

0xTim avatar Sep 26 '23 01:09 0xTim