-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
Query.scala
172 lines (148 loc) · 7.03 KB
/
Query.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*
* Copyright (c) 2002-2016 "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.frontend.v3_1.ast
import org.neo4j.cypher.internal.frontend.v3_1.{InputPosition, SemanticChecking, SemanticError, _}
case class Query(periodicCommitHint: Option[PeriodicCommitHint], part: QueryPart)(val position: InputPosition)
extends Statement with SemanticChecking {
override def returnColumns = part.returnColumns
override def semanticCheck =
part.semanticCheck chain
periodicCommitHint.semanticCheck chain
when(periodicCommitHint.nonEmpty && !part.containsUpdates) {
SemanticError("Cannot use periodic commit in a non-updating query", periodicCommitHint.get.position)
}
}
sealed trait QueryPart extends ASTNode with ASTPhrase with SemanticCheckable {
def containsUpdates: Boolean
def returnColumns: List[String]
}
case class SingleQuery(clauses: Seq[Clause])(val position: InputPosition) extends QueryPart {
assert(clauses.nonEmpty)
override def containsUpdates =
clauses.exists {
case call: CallClause => !call.containsNoUpdates
case _: UpdateClause => true
case _ => false
}
override def returnColumns = clauses.last.returnColumns
override def semanticCheck =
checkOrder chain
checkClauses chain
checkIndexHints
private def checkIndexHints: SemanticCheck = s => {
val hints = clauses.collect { case m: Match => m.hints }.flatten
val hasStartClause = clauses.exists(_.isInstanceOf[Start])
if (hints.nonEmpty && hasStartClause) {
SemanticCheckResult.error(s, SemanticError("Cannot use planner hints with start clause", hints.head.position))
} else {
SemanticCheckResult.success(s)
}
}
private def checkOrder: SemanticCheck = s => {
val (lastPair, errors) = clauses.sliding(2).foldLeft(Seq.empty[Clause], Vector.empty[SemanticError]) {
case ((_, semanticErrors), pair) =>
val optError = pair match {
case Seq(_: With, _: Start) =>
None
case Seq(clause, start: Start) =>
Some(SemanticError(s"WITH is required between ${clause.name} and ${start.name}", clause.position, start.position))
case Seq(match1: Match, match2: Match) if match1.optional && !match2.optional =>
Some(SemanticError(s"${match2.name} cannot follow OPTIONAL ${match1.name} (perhaps use a WITH clause between them)", match2.position, match1.position))
case Seq(clause: Return, _) =>
Some(SemanticError(s"${clause.name} can only be used at the end of the query", clause.position))
case Seq(_: UpdateClause, _: UpdateClause) =>
None
case Seq(_: UpdateClause, _: With) =>
None
case Seq(_: UpdateClause, _: Return) =>
None
case Seq(update: UpdateClause, clause) =>
Some(SemanticError(s"WITH is required between ${update.name} and ${clause.name}", clause.position, update.position))
case _ =>
None
}
(pair, optError.fold(semanticErrors)(semanticErrors :+ _))
}
val lastError = lastPair.last match {
case _: UpdateClause => None
case _: Return => None
case _: CallClause if clauses.size == 1 => None
case clause =>
Some(SemanticError(s"Query cannot conclude with ${clause.name} (must be RETURN or an update clause)", clause.position))
}
SemanticCheckResult(s, errors ++ lastError)
}
private def checkClauses: SemanticCheck = s => {
val result = clauses.foldLeft(SemanticCheckResult.success(s.newChildScope))((lastResult, clause) => clause match {
case c: HorizonClause =>
val closingResult = c.semanticCheck(lastResult.state)
val nextState = closingResult.state.newSiblingScope
val continuationResult = c.semanticCheckContinuation(closingResult.state.currentScope.scope)(nextState)
SemanticCheckResult(continuationResult.state, lastResult.errors ++ closingResult.errors ++ continuationResult.errors)
case _ =>
val result = clause.semanticCheck(lastResult.state)
SemanticCheckResult(result.state, lastResult.errors ++ result.errors)
})
SemanticCheckResult(result.state.popScope, result.errors)
}
}
sealed trait Union extends QueryPart with SemanticChecking {
def part: QueryPart
def query: SingleQuery
def returnColumns = query.returnColumns
def containsUpdates:Boolean = part.containsUpdates || query.containsUpdates
def semanticCheck: SemanticCheck =
checkUnionAggregation chain
withScopedState(part.semanticCheck) chain
withScopedState(query.semanticCheck) chain
checkColumnNamesAgree
private def checkColumnNamesAgree: SemanticCheck = (state: SemanticState) => {
val rootScope: Scope = state.currentScope.scope
// UNION queries form a chain in the shape of a reversed linked list.
// Therefore, the second scope is always a shallow scope, where the last one corresponds to the RETURN clause.
// The first one may either be another UNION query part or a single query.
val first :: second :: Nil = rootScope.children
val newFirst = part match {
case _: Union => // Union query parts always have two child scopes.
val _ :: newFirst :: Nil = first.children
newFirst
case _ => first
}
val errors = if (newFirst.children.last.symbolNames == second.children.last.symbolNames) {
Seq.empty
} else {
Seq(SemanticError("All sub queries in an UNION must have the same column names", position))
}
SemanticCheckResult(state, errors)
}
private def checkUnionAggregation: SemanticCheck = (part, this) match {
case (_: SingleQuery, _) => None
case (_: UnionAll, _: UnionAll) => None
case (_: UnionDistinct, _: UnionDistinct) => None
case _ => Some(SemanticError("Invalid combination of UNION and UNION ALL", position))
}
def unionedQueries: Seq[SingleQuery] = unionedQueries(Vector.empty)
private def unionedQueries(accum: Seq[SingleQuery]): Seq[SingleQuery] = part match {
case q: SingleQuery => accum :+ query :+ q
case u: Union => u.unionedQueries(accum :+ query)
}
}
final case class UnionAll(part: QueryPart, query: SingleQuery)(val position: InputPosition) extends Union
final case class UnionDistinct(part: QueryPart, query: SingleQuery)(val position: InputPosition) extends Union