-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
verifyBestPlan.scala
120 lines (110 loc) · 5.27 KB
/
verifyBestPlan.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.cypher.internal.compiler.v3_3.planner.logical.steps
import org.neo4j.cypher.internal.compiler.v3_3.planner.logical.plans.LogicalPlan
import org.neo4j.cypher.internal.compiler.v3_3.planner.logical.{LogicalPlanningContext, PlanTransformer}
import org.neo4j.cypher.internal.compiler.v3_3.spi.PlanContext
import org.neo4j.cypher.internal.frontend.v3_3.ast._
import org.neo4j.cypher.internal.frontend.v3_3.notification.{IndexHintUnfulfillableNotification, JoinHintUnfulfillableNotification}
import org.neo4j.cypher.internal.frontend.v3_3.{HintException, IndexHintException, InternalException, JoinHintException}
import org.neo4j.cypher.internal.ir.v3_3.PlannerQuery
object verifyBestPlan extends PlanTransformer[PlannerQuery] {
def apply(plan: LogicalPlan, expected: PlannerQuery)(implicit context: LogicalPlanningContext): LogicalPlan = {
val constructed = plan.solved
if (expected != constructed) {
val unfulfillableIndexHints = findUnfulfillableIndexHints(expected, context.planContext)
val unfulfillableJoinHints = findUnfulfillableJoinHints(expected, context.planContext)
val expectedWithoutHints = expected.withoutHints(unfulfillableIndexHints ++ unfulfillableJoinHints)
if (expectedWithoutHints != constructed) {
val a: PlannerQuery = expected.withoutHints(expected.allHints)
val b: PlannerQuery = constructed.withoutHints(constructed.allHints)
if (a != b) {
// unknown planner issue failed to find plan (without regard for differences in hints)
throw new InternalException(s"Expected \n$expected \n\n\nInstead, got: \n$constructed")
} else {
// unknown planner issue failed to find plan matching hints (i.e. "implicit hints")
val expectedHints = expected.allHints
val actualHints = constructed.allHints
val missing = expectedHints -- actualHints
def out(h: Set[Hint]) = h.mkString("`", ", ", "`")
val details = if (missing.isEmpty)
s"""Expected:
|${out(expectedHints)}
|
|Instead, got:
|${out(actualHints)}""".stripMargin
else
s"Could not solve these hints: ${out(missing)}"
val message =
s"""Failed to fulfil the hints of the query.
|$details""".stripMargin
throw new HintException(message)
}
} else {
processUnfulfilledIndexHints(context, unfulfillableIndexHints)
processUnfulfilledJoinHints(context, unfulfillableJoinHints)
}
}
plan
}
private def processUnfulfilledIndexHints(context: LogicalPlanningContext, hints: Set[UsingIndexHint]) = {
if (hints.nonEmpty) {
// hints referred to non-existent indexes ("explicit hints")
if (context.useErrorsOverWarnings) {
val firstIndexHint = hints.head
throw new IndexHintException(firstIndexHint.variable.name, firstIndexHint.label.name, firstIndexHint.properties.map(_.name), "No such index")
} else {
hints.foreach { hint =>
context.notificationLogger.log(IndexHintUnfulfillableNotification(hint.label.name, hint.properties.map(_.name)))
}
}
}
}
private def processUnfulfilledJoinHints(context: LogicalPlanningContext, hints: Set[UsingJoinHint]) = {
if (hints.nonEmpty) {
// we were unable to plan computeHash join on some requested nodes
if (context.useErrorsOverWarnings) {
val firstJoinHint = hints.head
throw new JoinHintException(firstJoinHint.variables.map(_.name).reduceLeft(_ + ", " + _), "Unable to plan computeHash join")
} else {
hints.foreach { hint =>
context.notificationLogger.log(JoinHintUnfulfillableNotification(hint.variables.map(_.name).toIndexedSeq))
}
}
}
}
private def findUnfulfillableIndexHints(query: PlannerQuery, planContext: PlanContext): Set[UsingIndexHint] = {
query.allHints.flatMap {
// using index name:label(property1,property2)
case UsingIndexHint(_, LabelName(label), properties)
if planContext.indexGet(label, properties.map(_.name)).isDefined ||
planContext.uniqueIndexGet(label, properties.map(_.name)).isDefined => None
// no such index exists
case hint: UsingIndexHint => Some(hint)
// don't care about other hints
case _ => None
}
}
private def findUnfulfillableJoinHints(query: PlannerQuery, planContext: PlanContext): Set[UsingJoinHint] = {
query.allHints.collect {
case hint: UsingJoinHint => hint
}
}
}