From 65daf9ff87e2da19c84de4ecf9074ebddf2e3f80 Mon Sep 17 00:00:00 2001 From: apstndb Date: Fri, 25 Sep 2020 19:54:41 +0900 Subject: [PATCH] Add Condition of Hash Join as a supported predicate (#88) * Fix comment to add Condition of Hash Join as known predicates * Add a test case of Hash Join * Do gofmt --- query_plan.go | 2 +- query_plan_test.go | 40 ++++ statement.go | 4 +- testdata/plans/hash_join.input.json | 299 ++++++++++++++++++++++++++++ 4 files changed, 342 insertions(+), 3 deletions(-) create mode 100644 testdata/plans/hash_join.input.json diff --git a/query_plan.go b/query_plan.go index 8b29c37..8544365 100644 --- a/query_plan.go +++ b/query_plan.go @@ -121,7 +121,7 @@ type QueryPlanRow struct { } func isPredicate(planNodes []*pb.PlanNode, childLink *pb.PlanNode_ChildLink) bool { - // Known predicates are Condition(Filter) or Seek Condition/Residual Condition(FilterScan) or Split Range(Distributed Union). + // Known predicates are Condition(Filter/Hash Join) or Seek Condition/Residual Condition(FilterScan) or Split Range(Distributed Union). // Agg is a Function but not a predicate. child := planNodes[childLink.ChildIndex] if child.DisplayName != "Function" { diff --git a/query_plan_test.go b/query_plan_test.go index c91afd6..198c84e 100644 --- a/query_plan_test.go +++ b/query_plan_test.go @@ -68,6 +68,46 @@ func TestRenderTreeUsingTestdataPlans(t *testing.T) { Text: " +- Index Scan (Index: SingersByFirstLastName)", }, }}, + { + /* + Original Query: + SELECT a.AlbumTitle, s.SongName + FROM Albums AS a HASH JOIN Songs AS s + ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId; + */ + title: "Hash Join", + file: "testdata/plans/hash_join.input.json", + want: []QueryPlanRow{ + { + ID: 0, + Text: "Distributed Union", + }, + { + ID: 1, + Text: "+- Serialize Result", + }, + { + ID: 2, + Text: " +- Hash Join (join_type: INNER)", + Predicates: []string{"Condition: (($SingerId = $SingerId_1) AND ($AlbumId = $AlbumId_1))"}, + }, + { + ID: 3, + Text: " +- [Build] Local Distributed Union", + }, + { + ID: 4, + Text: " | +- Table Scan (Full scan: true, Table: Albums)", + }, + { + ID: 8, + Text: " +- [Probe] Local Distributed Union", + }, + { + ID: 9, + Text: " +- Index Scan (Full scan: true, Index: SongsBySingerAlbumSongNameDesc)", + }, + }}, } { t.Run(test.title, func(t *testing.T) { b, err := ioutil.ReadFile(test.file) diff --git a/statement.go b/statement.go index 813eb01..4344d93 100644 --- a/statement.go +++ b/statement.go @@ -96,7 +96,7 @@ var ( ) var ( - explainColumnNames = []string{"ID", "Query_Execution_Plan (EXPERIMENTAL)"} + explainColumnNames = []string{"ID", "Query_Execution_Plan (EXPERIMENTAL)"} explainAnalyzeColumnNames = []string{"ID", "Query_Execution_Plan", "Rows_Returned", "Executions", "Total_Latency"} ) @@ -747,7 +747,7 @@ func (s *ExplainDmlStatement) Execute(session *Session) (*Result, error) { _, timestamp, queryPlan, err := runInNewOrExistRwTxForExplain(session, func() (int64, *pb.QueryPlan, error) { plan, err := session.rwTxn.AnalyzeQuery(session.ctx, spanner.NewStatement(s.Dml)) return 0, plan, err - } ) + }) if err != nil { return nil, err } diff --git a/testdata/plans/hash_join.input.json b/testdata/plans/hash_join.input.json new file mode 100644 index 0000000..ab99077 --- /dev/null +++ b/testdata/plans/hash_join.input.json @@ -0,0 +1,299 @@ +{ + "planNodes": [ + { + "childLinks": [ + { + "childIndex": 1 + }, + { + "childIndex": 23, + "type": "Split Range" + } + ], + "displayName": "Distributed Union", + "kind": "RELATIONAL", + "metadata": { + "subquery_cluster_node": "1" + } + }, + { + "childLinks": [ + { + "childIndex": 2 + }, + { + "childIndex": 21 + }, + { + "childIndex": 22 + } + ], + "displayName": "Serialize Result", + "index": 1, + "kind": "RELATIONAL" + }, + { + "childLinks": [ + { + "childIndex": 3, + "type": "Build" + }, + { + "childIndex": 8, + "type": "Probe" + }, + { + "childIndex": 13, + "type": "Condition" + }, + { + "childIndex": 20, + "type": "Build", + "variable": "AlbumTitle'" + } + ], + "displayName": "Hash Join", + "index": 2, + "kind": "RELATIONAL", + "metadata": { + "join_type": "INNER" + } + }, + { + "childLinks": [ + { + "childIndex": 4 + } + ], + "displayName": "Distributed Union", + "index": 3, + "kind": "RELATIONAL", + "metadata": { + "call_type": "Local", + "subquery_cluster_node": "4" + } + }, + { + "childLinks": [ + { + "childIndex": 5, + "variable": "SingerId" + }, + { + "childIndex": 6, + "variable": "AlbumId" + }, + { + "childIndex": 7, + "variable": "AlbumTitle" + } + ], + "displayName": "Scan", + "index": 4, + "kind": "RELATIONAL", + "metadata": { + "Full scan": "true", + "scan_target": "Albums", + "scan_type": "TableScan" + } + }, + { + "displayName": "Reference", + "index": 5, + "kind": "SCALAR", + "shortRepresentation": { + "description": "SingerId" + } + }, + { + "displayName": "Reference", + "index": 6, + "kind": "SCALAR", + "shortRepresentation": { + "description": "AlbumId" + } + }, + { + "displayName": "Reference", + "index": 7, + "kind": "SCALAR", + "shortRepresentation": { + "description": "AlbumTitle" + } + }, + { + "childLinks": [ + { + "childIndex": 9 + } + ], + "displayName": "Distributed Union", + "index": 8, + "kind": "RELATIONAL", + "metadata": { + "call_type": "Local", + "subquery_cluster_node": "9" + } + }, + { + "childLinks": [ + { + "childIndex": 10, + "variable": "AlbumId_1" + }, + { + "childIndex": 11, + "variable": "SingerId_1" + }, + { + "childIndex": 12, + "variable": "SongName" + } + ], + "displayName": "Scan", + "index": 9, + "kind": "RELATIONAL", + "metadata": { + "Full scan": "true", + "scan_target": "SongsBySingerAlbumSongNameDesc", + "scan_type": "IndexScan" + } + }, + { + "displayName": "Reference", + "index": 10, + "kind": "SCALAR", + "shortRepresentation": { + "description": "AlbumId" + } + }, + { + "displayName": "Reference", + "index": 11, + "kind": "SCALAR", + "shortRepresentation": { + "description": "SingerId" + } + }, + { + "displayName": "Reference", + "index": 12, + "kind": "SCALAR", + "shortRepresentation": { + "description": "SongName" + } + }, + { + "childLinks": [ + { + "childIndex": 14 + }, + { + "childIndex": 17 + } + ], + "displayName": "Function", + "index": 13, + "kind": "SCALAR", + "shortRepresentation": { + "description": "(($SingerId = $SingerId_1) AND ($AlbumId = $AlbumId_1))" + } + }, + { + "childLinks": [ + { + "childIndex": 15 + }, + { + "childIndex": 16 + } + ], + "displayName": "Function", + "index": 14, + "kind": "SCALAR", + "shortRepresentation": { + "description": "($SingerId = $SingerId_1)" + } + }, + { + "displayName": "Reference", + "index": 15, + "kind": "SCALAR", + "shortRepresentation": { + "description": "$SingerId" + } + }, + { + "displayName": "Reference", + "index": 16, + "kind": "SCALAR", + "shortRepresentation": { + "description": "$SingerId_1" + } + }, + { + "childLinks": [ + { + "childIndex": 18 + }, + { + "childIndex": 19 + } + ], + "displayName": "Function", + "index": 17, + "kind": "SCALAR", + "shortRepresentation": { + "description": "($AlbumId = $AlbumId_1)" + } + }, + { + "displayName": "Reference", + "index": 18, + "kind": "SCALAR", + "shortRepresentation": { + "description": "$AlbumId" + } + }, + { + "displayName": "Reference", + "index": 19, + "kind": "SCALAR", + "shortRepresentation": { + "description": "$AlbumId_1" + } + }, + { + "displayName": "Reference", + "index": 20, + "kind": "SCALAR", + "shortRepresentation": { + "description": "$AlbumTitle" + } + }, + { + "displayName": "Reference", + "index": 21, + "kind": "SCALAR", + "shortRepresentation": { + "description": "$AlbumTitle'" + } + }, + { + "displayName": "Reference", + "index": 22, + "kind": "SCALAR", + "shortRepresentation": { + "description": "$SongName" + } + }, + { + "displayName": "Constant", + "index": 23, + "kind": "SCALAR", + "shortRepresentation": { + "description": "true" + } + } + ] +}