diff --git a/query/graphql/mapper/commitSelect.go b/query/graphql/mapper/commitSelect.go index fcf5be8c2d..b69eb91350 100644 --- a/query/graphql/mapper/commitSelect.go +++ b/query/graphql/mapper/commitSelect.go @@ -37,6 +37,9 @@ type CommitSelect struct { // The field for which commits have been requested. FieldName client.Option[string] + // The maximum depth to yield results for. + Depth client.Option[uint64] + // The parent Cid for which commit information has been requested. Cid string } diff --git a/query/graphql/mapper/mapper.go b/query/graphql/mapper/mapper.go index d15f94c5f6..43da257006 100644 --- a/query/graphql/mapper/mapper.go +++ b/query/graphql/mapper/mapper.go @@ -789,6 +789,7 @@ func ToCommitSelect(ctx context.Context, txn datastore.Txn, parsed *parser.Commi DocKey: parsed.DocKey, Type: CommitType(parsed.Type), FieldName: parsed.FieldName, + Depth: parsed.Depth, Cid: parsed.Cid, }, nil } diff --git a/query/graphql/parser/commit.go b/query/graphql/parser/commit.go index 95cd18a4b4..3bc314e9a5 100644 --- a/query/graphql/parser/commit.go +++ b/query/graphql/parser/commit.go @@ -45,6 +45,7 @@ type CommitSelect struct { DocKey string FieldName client.Option[string] Cid string + Depth client.Option[uint64] Limit *parserTypes.Limit OrderBy *parserTypes.OrderBy @@ -119,6 +120,13 @@ func parseCommitSelect(field *ast.Field) (*CommitSelect, error) { commit.Limit = &parserTypes.Limit{} } commit.Limit.Offset = offset + } else if prop == parserTypes.DepthClause { + raw := argument.Value.(*ast.IntValue) + depth, err := strconv.ParseUint(raw.Value, 10, 64) + if err != nil { + return nil, err + } + commit.Depth = client.Some(depth) } } diff --git a/query/graphql/parser/types/types.go b/query/graphql/parser/types/types.go index ebf597ff0f..e25d9ba83d 100644 --- a/query/graphql/parser/types/types.go +++ b/query/graphql/parser/types/types.go @@ -73,6 +73,7 @@ const ( LimitClause = "limit" OffsetClause = "offset" OrderClause = "order" + DepthClause = "depth" AverageFieldName = "_avg" CountFieldName = "_count" diff --git a/query/graphql/planner/commit.go b/query/graphql/planner/commit.go index dfe06e26c5..0875c4ed10 100644 --- a/query/graphql/planner/commit.go +++ b/query/graphql/planner/commit.go @@ -183,7 +183,12 @@ func (p *Planner) commitSelectAll(parsed *mapper.CommitSelect) (*commitSelectNod headset.key = key } - dag.depthLimit = math.MaxUint32 // infinite depth + if parsed.Depth.HasValue() { + dag.depthLimit = uint32(parsed.Depth.Value()) + } else { + // infinite depth + dag.depthLimit = math.MaxUint32 + } // dag.key = &key commit := &commitSelectNode{ p: p, diff --git a/query/graphql/planner/dagscan.go b/query/graphql/planner/dagscan.go index e372e6eac8..467aad2fad 100644 --- a/query/graphql/planner/dagscan.go +++ b/query/graphql/planner/dagscan.go @@ -268,6 +268,8 @@ func (n *dagScanNode) Next() (bool, error) { return false, errors.New("Headset scan node returned an invalid cid") } n.currentCid = &cid + // Reset the depthVisited for each head yielded by headset + n.depthVisited = 0 } else if n.cid != nil { if n.visitedNodes[n.cid.String()] { // If the requested cid has been visited, we are done and should return false diff --git a/query/graphql/schema/types/commits.go b/query/graphql/schema/types/commits.go index e6694a4c91..6769963918 100644 --- a/query/graphql/schema/types/commits.go +++ b/query/graphql/schema/types/commits.go @@ -113,6 +113,7 @@ var ( "cid": NewArgConfig(gql.ID), parserTypes.LimitClause: NewArgConfig(gql.Int), parserTypes.OffsetClause: NewArgConfig(gql.Int), + parserTypes.DepthClause: NewArgConfig(gql.Int), }, } diff --git a/tests/integration/query/commits/with_depth_test.go b/tests/integration/query/commits/with_depth_test.go new file mode 100644 index 0000000000..2f47df270e --- /dev/null +++ b/tests/integration/query/commits/with_depth_test.go @@ -0,0 +1,202 @@ +// Copyright 2022 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package commits + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestQueryCommitsWithDepth1(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Simple all commits query with depth 1", + Query: `query { + commits(depth: 1) { + cid + } + }`, + Docs: map[int][]string{ + 0: { + `{ + "Name": "John", + "Age": 21 + }`, + }, + }, + Results: []map[string]any{ + { + "cid": "bafybeidst2mzxhdoh4ayjdjoh4vibo7vwnuoxk3xgyk5mzmep55jklni2a", + }, + { + "cid": "bafybeihhypcsqt7blkrqtcmpl43eo3yunrog5pchox5naji6hisdme4swm", + }, + { + "cid": "bafybeid57gpbwi4i6bg7g357vwwyzsmr4bjo22rmhoxrwqvdxlqxcgaqvu", + }, + }, + } + + executeTestCase(t, test) +} + +func TestQueryCommitsWithDepth1WithUpdate(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Simple all commits query with depth 1, and doc updates", + Query: `query { + commits(depth: 1) { + cid + height + } + }`, + Docs: map[int][]string{ + 0: { + `{ + "Name": "John", + "Age": 21 + }`, + }, + }, + Updates: map[int]map[int][]string{ + 0: { + 0: { + `{ + "Age": 22 + }`, + }, + }, + }, + Results: []map[string]any{ + { + "cid": "bafybeicvef4ugls2dl7j4hibt2ahxss2i2i4bbgps7tkjiaoybp6q73mca", + "height": int64(2), + }, + { + // "Name" field head (unchanged from create) + "cid": "bafybeihhypcsqt7blkrqtcmpl43eo3yunrog5pchox5naji6hisdme4swm", + "height": int64(1), + }, + { + // "Age" field head + "cid": "bafybeibrbfg35mwggcj4vnskak4qn45hp7fy5a4zp2n34sbq5vt5utr6pq", + "height": int64(2), + }, + }, + } + + executeTestCase(t, test) +} + +func TestQueryCommitsWithDepth2WithUpdate(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Simple all commits query with depth 2, and doc updates", + Query: `query { + commits(depth: 2) { + cid + height + } + }`, + Docs: map[int][]string{ + 0: { + `{ + "Name": "John", + "Age": 21 + }`, + }, + }, + Updates: map[int]map[int][]string{ + 0: { + 0: { + `{ + "Age": 22 + }`, + `{ + "Age": 23 + }`, + }, + }, + }, + Results: []map[string]any{ + { + // Composite head + "cid": "bafybeiaxjhz6dna7fyf7tqo5hooilwvaezswd5xfsmb2lfgcy7tpzklikm", + "height": int64(3), + }, + { + // Composite head -1 + "cid": "bafybeicvef4ugls2dl7j4hibt2ahxss2i2i4bbgps7tkjiaoybp6q73mca", + "height": int64(2), + }, + { + // "Name" field head (unchanged from create) + "cid": "bafybeihhypcsqt7blkrqtcmpl43eo3yunrog5pchox5naji6hisdme4swm", + "height": int64(1), + }, + { + // "Age" field head + "cid": "bafybeid2tudsm4go5boq7yvz6pprtgaiddkazq2dip6c4fsqt3afhzexbq", + "height": int64(3), + }, + { + // "Age" field head -1 + "cid": "bafybeibrbfg35mwggcj4vnskak4qn45hp7fy5a4zp2n34sbq5vt5utr6pq", + "height": int64(2), + }, + }, + } + + executeTestCase(t, test) +} + +func TestQueryCommitsWithDepth1AndMultipleDocs(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Simple all commits query with depth 1", + Query: `query { + commits(depth: 1) { + cid + } + }`, + Docs: map[int][]string{ + 0: { + `{ + "Name": "John", + "Age": 21 + }`, + `{ + "Name": "Fred", + "Age": 25 + }`, + }, + }, + Results: []map[string]any{ + { + "cid": "bafybeidst2mzxhdoh4ayjdjoh4vibo7vwnuoxk3xgyk5mzmep55jklni2a", + }, + { + "cid": "bafybeihhypcsqt7blkrqtcmpl43eo3yunrog5pchox5naji6hisdme4swm", + }, + { + "cid": "bafybeid57gpbwi4i6bg7g357vwwyzsmr4bjo22rmhoxrwqvdxlqxcgaqvu", + }, + { + "cid": "bafybeiajhlicqju3thdnyemvparx35kg6vfb6sr3vuemhw7zjrulx2tkom", + }, + { + "cid": "bafybeifl4q2htt4sozl5dnxjqkpstpqbpkurgqc56dnn2bvtsora3srl2q", + }, + { + "cid": "bafybeigbxr6aavljpcfruccoxi43kxjb46locbb7mwupwzjxbz5whplydu", + }, + }, + } + + executeTestCase(t, test) +}