Skip to content

Commit

Permalink
Column aliasing is now supported
Browse files Browse the repository at this point in the history
  • Loading branch information
systay committed Nov 30, 2011
1 parent 79e2fe4 commit 5de22cf
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 24 deletions.
1 change: 1 addition & 0 deletions cypher/src/docs/dev/ql/return/index.txt
Expand Up @@ -11,6 +11,7 @@ include::return-nodes.txt[]
include::return-relationships.txt[]
include::return-property.txt[]
include::identifier-with-uncommon-characters.txt[]
include::column-alias.txt[]
include::optional-properties.txt[]
include::unique-results.txt[]

14 changes: 7 additions & 7 deletions cypher/src/main/scala/org/neo4j/cypher/commands/Query.scala
Expand Up @@ -24,18 +24,18 @@ object Query {
def start(startItems: StartItem*) = new QueryBuilder(startItems)
}

case class Query(returns: Return, start: Start, matching: Option[ Match ], where: Option[ Clause ],
aggregation: Option[ Aggregation ],
sort: Option[ Sort ], slice: Option[ Slice ], namedPaths: Option[ NamedPaths ])
case class Query(returns: Return, start: Start, matching: Option[Match], where: Option[Clause],
aggregation: Option[Aggregation],
sort: Option[Sort], slice: Option[Slice], namedPaths: Option[NamedPaths])

case class Return(columns: List[ String ], returnItems: ReturnItem*)
case class Return(columns: List[String], returnItems: ReturnItem*)

case class Start(startItems: StartItem*)

case class Match(patterns: Pattern*)

case class NamedPaths(paths: NamedPath*) extends Traversable[ Pattern ] {
def foreach[ U ](f: ( Pattern ) => U) {
case class NamedPaths(paths: NamedPath*) extends Traversable[Pattern] {
def foreach[U](f: (Pattern) => U) {
paths.flatten.foreach(f)
}
}
Expand All @@ -44,4 +44,4 @@ case class Aggregation(aggregationItems: AggregationItem*)

case class Sort(sortItems: SortItem*)

case class Slice(from: Option[ Value ], limit: Option[ Value ])
case class Slice(from: Option[Value], limit: Option[Value])
26 changes: 24 additions & 2 deletions cypher/src/main/scala/org/neo4j/cypher/commands/ReturnItem.scala
Expand Up @@ -29,6 +29,8 @@ abstract sealed class ReturnItem(val identifier: Identifier) extends (Map[String
def columnName = identifier.name

def concreteReturnItem = this

override def toString() = identifier.name
}

case class ValueReturnItem(value: Value) extends ReturnItem(value.identifier) {
Expand All @@ -39,22 +41,42 @@ case class ValueReturnItem(value: Value) extends ReturnItem(value.identifier) {
}
}

case class AliasReturnItem(inner: ReturnItem, newName:String) extends ReturnItem(Identifier(newName, inner.identifier.typ)) {
def apply(m: Map[String, Any]): Any = inner.apply(m)

def assertDependencies(source: Pipe) {
inner.assertDependencies(source)
}

override def toString() = inner.toString() + " AS " + newName
}


case class ValueAggregationItem(value: AggregationValue) extends AggregationItem(value.identifier) {

def assertDependencies(source: Pipe) {
value.checkAvailable(source.symbols)
}
def createAggregationFunction: AggregationFunction = value.createAggregationFunction

def createAggregationFunction: AggregationFunction = value.createAggregationFunction
}

abstract sealed class AggregationItem(identifier:Identifier) extends ReturnItem(identifier) {
abstract sealed class AggregationItem(identifier: Identifier) extends ReturnItem(identifier) {
def apply(m: Map[String, Any]): Map[String, Any] = m

def createAggregationFunction: AggregationFunction

override def toString() = identifier.name
}

case class AliasAggregationItem(inner:AggregationItem, newName:String) extends AggregationItem(Identifier(newName, inner.identifier.typ)) {
def assertDependencies(source: Pipe) {
inner.assertDependencies(source)
}

def createAggregationFunction: AggregationFunction = inner.createAggregationFunction
}


case class CountStar() extends AggregationItem(Identifier("count(*)", IntegerType())) {
def createAggregationFunction: AggregationFunction = new CountStarFunction
Expand Down
35 changes: 24 additions & 11 deletions cypher/src/main/scala/org/neo4j/cypher/parser/ReturnClause.scala
Expand Up @@ -19,28 +19,41 @@ package org.neo4j.cypher.parser
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import org.neo4j.cypher.commands._
import scala.util.parsing.combinator._

trait ReturnClause extends JavaTokenParsers with Tokens with ReturnItems {

def column = (aggregate | returnItem) ~ opt(ignoreCase("AS") ~> identity) ^^ {
case returnItem ~ alias => alias match {
case None => returnItem
case Some(newColumnName) => returnItem match {
case x: AggregationItem => AliasAggregationItem(x, newColumnName)
case x => AliasReturnItem(x, newColumnName)
}
}
}

def returns: Parser[(Return, Option[Aggregation])] = ignoreCase("return") ~> opt("distinct") ~ rep1sep((aggregate | returnItem), ",") ^^
{ case distinct ~ items => {
val list = items.filter(_.isInstanceOf[AggregationItem]).map(_.asInstanceOf[AggregationItem])
def returns: Parser[(Return, Option[Aggregation])] = ignoreCase("return") ~> opt("distinct") ~ rep1sep(column, ",") ^^ {
case distinct ~ items => {
val aggregationItems = items.filter(_.isInstanceOf[AggregationItem]).map(_.asInstanceOf[AggregationItem])

val none: Option[Aggregation] = distinct match {
case Some(x) => Some(Aggregation())
case None => None
}

(
Return(items.map(_.columnName).toList, items.filter(!_.isInstanceOf[AggregationItem]): _*),
list match {
case List() => none
case _ => Some(Aggregation(list : _*))
}
)
}}
val aggregation = aggregationItems match {
case List() => none
case _ => Some(Aggregation(aggregationItems: _*))
}

val returnItems = Return(items.map(_.columnName).toList, items.filter(!_.isInstanceOf[AggregationItem]): _*)

(returnItems, aggregation)
}
}


}
Expand Down
Expand Up @@ -133,7 +133,7 @@ private void createTenNodes() {
public void exampleConsole() throws Exception {
Query query = CypherParser.parseConsole(
//START SNIPPET: Identifier
"start n=node(0) return n.NOT_EXISTING"
"start n=node(0) return n.NOT_EXISTING, n.`property with spaces in it`"
//END SNIPPET: Identifier
);

Expand Down
13 changes: 10 additions & 3 deletions cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala
Expand Up @@ -30,10 +30,17 @@ import org.scalatest.Assertions

class CypherParserTest extends JUnitSuite with Assertions {
@Test def shouldParseEasiestPossibleQuery() {
val q = Query.
testQuery("start s = NODE(1) return s",
Query.
start(NodeById("s", 1)).
returns(ValueReturnItem(EntityValue("s"))))
}

@Test def shouldHandleAliasingOfColumnNames() {
testQuery("start s = NODE(1) return s as somethingElse",
Query.
start(NodeById("s", 1)).
returns(ValueReturnItem(EntityValue("s")))
testQuery("start s = NODE(1) return s", q)
returns(AliasReturnItem(ValueReturnItem(EntityValue("s")), "somethingElse")))
}

@Test def sourceIsAnIndex() {
Expand Down
22 changes: 22 additions & 0 deletions cypher/src/test/scala/org/neo4j/cypher/ExecutionEngineTest.scala
Expand Up @@ -1239,6 +1239,28 @@ return a
assert(List(a) === result.columnAs[Node]("a").toList)
}

@Test def shouldSupportColumnRenaming() {
val a = createNode(Map("name" -> "Andreas"))

val result = parseAndExecute("""
start a = node(1)
return a as OneLove
""")

assert(List(a) === result.columnAs[Node]("OneLove").toList)
}

@Test def shouldSupportColumnRenamingForAggregatesAsWell() {
val a = createNode(Map("name" -> "Andreas"))

val result = parseAndExecute("""
start a = node(1)
return count(*) as OneLove
""")

assert(List(1) === result.columnAs[Node]("OneLove").toList)
}

@Ignore("Should be supported, but doesn't work")
@Test def shouldBeAbleToQueryNumericIndexes() {
val a = createNode("x" -> 5)
Expand Down
Expand Up @@ -87,4 +87,13 @@ like this:""",
returns = """The node named B, but only once.""",
(p) => assertEquals(List(node("B")), p.columnAs[Node]("b").toList))
}

@Test def column_aliasing() {
testQuery(
title = "Column alias",
text = """If the name of the column should be different from the expression used, you can rename it by using AS <new name>.""",
queryText = """start a=node(%A%) return a.age AS SomethingTotallyDifferent""",
returns = """Returns the age property of a node, but renames the column.""",
(p) => assertEquals(List(55), p.columnAs[Node]("SomethingTotallyDifferent").toList))
}
}

0 comments on commit 5de22cf

Please sign in to comment.