From 630ad068f1818124a89eda0748ad46282cad7df1 Mon Sep 17 00:00:00 2001
From: Shahzad Lone <shahzadlone@gmail.com>
Date: Fri, 10 Jun 2022 12:35:58 -0400
Subject: [PATCH] feat: Add ability to explain updateNode attributes.

---
 query/graphql/planner/update.go               |  26 +-
 .../update/explain_simple_update_test.go      | 307 ++++++++++++++++++
 2 files changed, 331 insertions(+), 2 deletions(-)
 create mode 100644 tests/integration/mutation/simple/update/explain_simple_update_test.go

diff --git a/query/graphql/planner/update.go b/query/graphql/planner/update.go
index 104283f4dc..c20086e16b 100644
--- a/query/graphql/planner/update.go
+++ b/query/graphql/planner/update.go
@@ -11,6 +11,8 @@
 package planner
 
 import (
+	"encoding/json"
+
 	"github.com/sourcenetwork/defradb/client"
 	"github.com/sourcenetwork/defradb/core"
 	"github.com/sourcenetwork/defradb/db/base"
@@ -136,12 +138,32 @@ func (n *updateNode) Close() error {
 	return n.results.Close()
 }
 
-func (n *updateNode) Source() planNode { return nil }
+func (n *updateNode) Source() planNode { return n.results }
 
 // Explain method returns a map containing all attributes of this node that
 // are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
 func (n *updateNode) Explain() (map[string]interface{}, error) {
-	return map[string]interface{}{}, nil
+	explainerMap := map[string]interface{}{}
+
+	// Add the document id(s) that request wants to update.
+	explainerMap[idsLabel] = n.ids
+
+	// Add the filter attribute if it exists, otherwise have it nil.
+	if n.filter == nil || n.filter.Conditions == nil {
+		explainerMap[filterLabel] = nil
+	} else {
+		explainerMap[filterLabel] = n.filter.Conditions
+	}
+
+	// Add the attribute that represents the patch to update with.
+	data := map[string]interface{}{}
+	err := json.Unmarshal([]byte(n.patch), &data)
+	if err != nil {
+		return nil, err
+	}
+	explainerMap[dataLabel] = data
+
+	return explainerMap, nil
 }
 
 func (p *Planner) UpdateDocs(parsed *parser.Mutation) (planNode, error) {
diff --git a/tests/integration/mutation/simple/update/explain_simple_update_test.go b/tests/integration/mutation/simple/update/explain_simple_update_test.go
new file mode 100644
index 0000000000..4a16c1aada
--- /dev/null
+++ b/tests/integration/mutation/simple/update/explain_simple_update_test.go
@@ -0,0 +1,307 @@
+// 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 update
+
+import (
+	"testing"
+
+	testUtils "github.com/sourcenetwork/defradb/tests/integration"
+	simpleTests "github.com/sourcenetwork/defradb/tests/integration/mutation/simple"
+)
+
+type dataMap = map[string]interface{}
+
+func TestExplainSimpleMutationUpdateWithBooleanFilter(t *testing.T) {
+	tests := []testUtils.QueryTestCase{
+
+		{
+			Description: "Explain simple update mutation with boolean equals filter, multiple rows",
+
+			Query: `mutation @explain {
+						update_user(
+							filter: {
+								verified: {
+									_eq: true
+								}
+							},
+							data: "{\"points\": 59}"
+						) {
+							_key
+							name
+							points
+						}
+					}`,
+
+			Docs: map[int][]string{
+				0: {
+					(`{
+					"name": "John",
+					"age": 27,
+					"verified": true,
+					"points": 42.1
+				}`),
+					(`{
+					"name": "Bob",
+					"age": 39,
+					"verified": true,
+					"points": 66.6
+				}`)},
+			},
+
+			Results: []dataMap{
+				{
+					"explain": dataMap{
+						"updateNode": dataMap{
+							"data": dataMap{
+								"points": float64(59),
+							},
+							"filter": dataMap{
+								"verified": dataMap{
+									"$eq": true,
+								},
+							},
+							"ids": []string(nil),
+							"selectTopNode": dataMap{
+								"renderNode": dataMap{
+									"selectNode": dataMap{
+										"filter": nil,
+										"scanNode": dataMap{
+											"collectionID":   "1",
+											"collectionName": "user",
+											"filter":         nil,
+											"spans":          []dataMap{},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		simpleTests.ExecuteTestCase(t, test)
+	}
+}
+
+func TestExplainSimpleMutationUpdateWithIdInFilter(t *testing.T) {
+	test := testUtils.QueryTestCase{
+
+		Description: "Explain simple update mutation with id in filter, multiple rows",
+
+		Query: `mutation @explain {
+					update_user(
+						ids: [
+							"bae-0a24cf29-b2c2-5861-9d00-abd6250c475d",
+							"bae-958c9334-73cf-5695-bf06-cf06826babfa"
+						],
+						data: "{\"points\": 59}"
+					) {
+						_key
+						name
+						points
+					}
+				}`,
+
+		Docs: map[int][]string{
+			0: {
+				(`{
+				"name": "John",
+				"age": 27,
+				"verified": true,
+				"points": 42.1
+			}`),
+				(`{
+				"name": "Bob",
+				"age": 39,
+				"verified": false,
+				"points": 66.6
+			}`)},
+		},
+
+		Results: []dataMap{
+			{
+				"explain": dataMap{
+					"updateNode": dataMap{
+						"data": dataMap{
+							"points": float64(59),
+						},
+						"filter": nil,
+						"ids": []string{
+							"bae-0a24cf29-b2c2-5861-9d00-abd6250c475d",
+							"bae-958c9334-73cf-5695-bf06-cf06826babfa",
+						},
+						"selectTopNode": dataMap{
+							"renderNode": dataMap{
+								"selectNode": dataMap{
+									"filter": nil,
+									"scanNode": dataMap{
+										"collectionID":   "1",
+										"collectionName": "user",
+										"filter":         nil,
+										"spans":          []dataMap{},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	simpleTests.ExecuteTestCase(t, test)
+}
+
+func TestExplainSimpleMutationUpdateWithIdEqualsFilter(t *testing.T) {
+	test := testUtils.QueryTestCase{
+
+		Description: "Explain simple update mutation with id equals filter, multiple rows but single match",
+
+		Query: `mutation @explain {
+					update_user(
+						id: "bae-0a24cf29-b2c2-5861-9d00-abd6250c475d",
+						data: "{\"points\": 59}"
+					) {
+						_key
+						name
+						points
+					}
+				}`,
+
+		Docs: map[int][]string{
+			0: {
+				(`{
+				"name": "John",
+				"age": 27,
+				"verified": true,
+				"points": 42.1
+			}`),
+				(`{
+				"name": "Bob",
+				"age": 39,
+				"verified": false,
+				"points": 66.6
+			}`)},
+		},
+
+		Results: []dataMap{
+			{
+				"explain": dataMap{
+					"updateNode": dataMap{
+						"data": dataMap{
+							"points": float64(59),
+						},
+						"filter": nil,
+						"ids": []string{
+							"bae-0a24cf29-b2c2-5861-9d00-abd6250c475d",
+						},
+						"selectTopNode": dataMap{
+							"renderNode": dataMap{
+								"selectNode": dataMap{
+									"filter": nil,
+									"scanNode": dataMap{
+										"collectionID":   "1",
+										"collectionName": "user",
+										"filter":         nil,
+										"spans":          []dataMap{},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	simpleTests.ExecuteTestCase(t, test)
+}
+
+func TestExplainSimpleMutationUpdateWithIdAndFilter(t *testing.T) {
+	test := testUtils.QueryTestCase{
+
+		Description: "Explain simple update mutation with ids and filter, multiple rows",
+
+		Query: `mutation @explain {
+					update_user(
+						filter: {
+							verified: {
+								_eq: true
+							}
+						},
+						ids: [
+							"bae-0a24cf29-b2c2-5861-9d00-abd6250c475d",
+							"bae-958c9334-73cf-5695-bf06-cf06826babfa"
+						],
+						data: "{\"points\": 59}"
+					) {
+						_key
+						name
+						points
+					}
+				}`,
+
+		Docs: map[int][]string{
+			0: {
+				(`{
+				"name": "John",
+				"age": 27,
+				"verified": true,
+				"points": 42.1
+			}`),
+				(`{
+				"name": "Bob",
+				"age": 39,
+				"verified": false,
+				"points": 66.6
+			}`)},
+		},
+
+		Results: []dataMap{
+			{
+				"explain": dataMap{
+					"updateNode": dataMap{
+						"data": dataMap{
+							"points": float64(59),
+						},
+						"filter": dataMap{
+							"verified": dataMap{
+								"$eq": true,
+							},
+						},
+						"ids": []string{
+							"bae-0a24cf29-b2c2-5861-9d00-abd6250c475d",
+							"bae-958c9334-73cf-5695-bf06-cf06826babfa",
+						},
+						"selectTopNode": dataMap{
+							"renderNode": dataMap{
+								"selectNode": dataMap{
+									"filter": nil,
+									"scanNode": dataMap{
+										"collectionID":   "1",
+										"collectionName": "user",
+										"filter":         nil,
+										"spans":          []dataMap{},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	simpleTests.ExecuteTestCase(t, test)
+}