sourcenetwork / defradb

DefraDB is a Peer-to-Peer Edge Database. It's the core data storage system for the Source Network Ecosystem, built with IPLD, LibP2P, CRDTs, and Semantic open web properties.
454 stars 45 forks source link

Enable filtering one-many related objects by number of related objects #2820

Open islamaliev opened 4 months ago

islamaliev commented 4 months ago

At the moment it is possible to filter secondary objects by certain criteria in primary objects. Like so:

query {
    Author(filter: {published: {rating: {_gt: 4.8}}, age: {_gt: 63}}) {
        name
    }
}

But it is not possible to find secondary objects that have no related objects:

query {
    Author(filter: {published: {_eq: null}) {
        name
    }
}

Would be also great to be able to request objects by number of related objects

query {
    Author(filter: {published: {_len: 2}) {
        name
    }
}

But for this a new _len operator needs to be implemented.

Here is a full integration test:

var bookAuthorGQLSchema = (`
    type Book {
        name: String
        rating: Float
        author: Author
    }

    type Author {
        name: String
        age: Int
        verified: Boolean
        published: [Book]
    }
`)

func TestQueryOneToMany_IfFilteredByEmptyRelations_ShouldFind(t *testing.T) {
    test := testUtils.TestCase{
        Actions: []any{
            testUtils.SchemaUpdate{
                Schema: bookAuthorGQLSchema,
            },
            testUtils.CreateDoc{
                CollectionID: 0,
                Doc: `{
                    "name": "Painted House",
                    "rating": 4.9,
                    "author_id": "bae-e1ea288f-09fa-55fa-b0b5-0ac8941ea35b"
                }`,
            },
            testUtils.CreateDoc{
                CollectionID: 0,
                Doc: `{
                    "name": "A Time for Mercy",
                    "rating": 4.5,
                    "author_id": "bae-e1ea288f-09fa-55fa-b0b5-0ac8941ea35b"
                    }`,
            },
            testUtils.CreateDoc{
                CollectionID: 0,
                Doc: `{
                    "name": "Theif Lord",
                    "rating": 4.8,
                    "author_id": "bae-72e8c691-9f20-55e7-9228-8af1cf54cace"
                }`,
            },
            testUtils.CreateDoc{
                CollectionID: 1,
                // bae-e1ea288f-09fa-55fa-b0b5-0ac8941ea35b
                Doc: `{
                    "name": "John Grisham",
                    "age": 65,
                    "verified": true
                }`,
            },
            testUtils.CreateDoc{
                CollectionID: 1,
                // bae-72e8c691-9f20-55e7-9228-8af1cf54cace
                Doc: `{
                    "name": "Cornelia Funke",
                    "age": 62,
                    "verified": false
                }`,
            },
            testUtils.CreateDoc{
                CollectionID: 1,
                // bae-6bf29c1c-7112-5f4f-bfae-1c039479acf6
                Doc: `{
                    "name": "John Tolkien",
                    "age": 70,
                    "verified": true
                }`,
            },
            testUtils.Request{
                Request: `query {
                    Author(filter: {published: {_eq: null}) {
                        name
                    }
                }`,
                Results: []map[string]any{{
                    "name": "John Tolkien",
                }},
            },
            // would be nice to have this as well
            //testUtils.Request{
            //  Request: `query {
            //      Author(filter: {published: {_len: 2}) {
            //          name
            //      }
            //  }`,
            //  Results: []map[string]any{{
            //      "name": "John Grisham",
            //  }},
            //},
        },
    }
    testUtils.ExecuteTestCase(t, test)
}
AndrewSisley commented 2 months ago

Note: Author(filter: {published: {_len: 2}) { is just filtering by aggregate (_count), and so is already solved by views, long term this would be handled by alias targeting for those who don't want to bother creating views.

In a one-many this also applies to Author(filter: {published: {_eq: null}), which is just another way of wording Author(filter: {published: {_len: 0}) {

I suggest we close this ticket.