diff options
-rw-r--r-- | src/main/scala/com/google/gimd/query/Node.scala | 44 | ||||
-rw-r--r-- | src/main/scala/com/google/gimd/query/NodeOps.scala | 75 | ||||
-rw-r--r-- | src/main/scala/com/google/gimd/query/Query.scala | 61 | ||||
-rw-r--r-- | src/test/scala/com/google/gimd/query/QueryASTTestCase.scala | 87 |
4 files changed, 267 insertions, 0 deletions
diff --git a/src/main/scala/com/google/gimd/query/Node.scala b/src/main/scala/com/google/gimd/query/Node.scala new file mode 100644 index 0000000..32aad1a --- /dev/null +++ b/src/main/scala/com/google/gimd/query/Node.scala @@ -0,0 +1,44 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gimd.query + +import com.google.gimd.FieldSpecOne + +/** + * Base class for all nodes in Query AST. + * + * NOTE: As for now Node does only track the type (T) of the value that comes from + * evaluating expression held by Node given specific instance defined by UserType. + * However, Node does not track the type of UserType so it's possible that Node's + * expression would be applied to wrong instance. This will be fixed in a future. + */ +abstract class Node[T] + +/** + * Node that stores FieldSpec. This node acts as placeholder in query for a given field of specific instance + * defined by UserType. + */ +final case class FieldSpecOneNode[T](val fieldSpecOne: FieldSpecOne[_, T]) extends Node[T] + +/** + * Node that holds a constant value of type T. + */ +final case class ConstNode[T](val value: T) extends Node[T] + +/** + * Node that holds Predicate which (in turn) holds arbitrary function T => Boolean where T is a type + * of instance that UserType is bound to. + */ +final case class PredicateNode[T](p: Predicate[T]) extends Node[Boolean] diff --git a/src/main/scala/com/google/gimd/query/NodeOps.scala b/src/main/scala/com/google/gimd/query/NodeOps.scala new file mode 100644 index 0000000..41953ab --- /dev/null +++ b/src/main/scala/com/google/gimd/query/NodeOps.scala @@ -0,0 +1,75 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gimd.query + +/** + * Base trait for all traits defining operations on given Node[T] stored in leftOperand. + */ +trait NodeOps[T] { + val leftOperand: Node[T] +} + +/** + * Operations applicable to all Node[T] instances. + * + * Since all primitive data types used in Gimd have equality and (natural) total order defined + * we can provide operators related to equality and ordering here. + */ +trait AllNodeOps[T] extends NodeOps[T] { + import AllNodeOps._ + def is(right: Node[T]) = Is(leftOperand, right) + def ===(right: Node[T]) = is(right) + def isNot(right: Node[T]) = !is(right) + def !==(right: Node[T]) = isNot(right) + + def <(right: Node[T]) = Relational("<", leftOperand, right) + def >(right: Node[T]) = !(<(right)) && !==(right) + def <=(right: Node[T]) = <(right) || ===(right) + def >=(right: Node[T]) = !(<(right)) +} + +object AllNodeOps { + case class Is[T](left: Node[T], right: Node[T]) extends Node[Boolean] with BooleanNodeOps { + val leftOperand = this + } + + case class Relational[T](name: String, left: Node[T], right: Node[T]) extends Node[Boolean] with BooleanNodeOps { + val leftOperand = this + } +} + +/** + * This trait defines boolean operations thus is applicable to Node[Boolean] + */ +trait BooleanNodeOps extends AllNodeOps[Boolean] { + import BooleanNodeOps._ + def && (right: Node[Boolean]) = And(leftOperand, right) + def || (right: Node[Boolean]) = Or(leftOperand, right) + def unary_! = Not(leftOperand) +} + +object BooleanNodeOps { + case class And(left: Node[Boolean], right: Node[Boolean]) extends Node[Boolean] with BooleanNodeOps { + val leftOperand = this + } + + case class Or(left: Node[Boolean], right: Node[Boolean]) extends Node[Boolean] with BooleanNodeOps { + val leftOperand = this + } + + case class Not(left: Node[Boolean]) extends Node[Boolean] with BooleanNodeOps { + val leftOperand = this + } +} diff --git a/src/main/scala/com/google/gimd/query/Query.scala b/src/main/scala/com/google/gimd/query/Query.scala new file mode 100644 index 0000000..4251b53 --- /dev/null +++ b/src/main/scala/com/google/gimd/query/Query.scala @@ -0,0 +1,61 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gimd.query + +import com.google.gimd.{FieldSpecOne, UserType} + +/** + * Class that holds a Query AST defined for specific UserType. + */ +final class Query[U <: UserType[_]](val ut: U, val cond: List[Node[Boolean]]) { + + def where(f: U => Node[Boolean]) = new Query[U](ut, f(ut) :: cond) + +} + +object Query { + + //this alias is needed due to bug in Scala 2.7.x. It's been fixed in 2.8.0 so once we switch to it + //this can be removed + type UserType_ = UserType[_] + + implicit def userType2Query[U <: UserType_](ut: U) = new Query[U](ut, Nil) + + //a few conversions to Node[T] + implicit def fieldSpecOne2Node[T](fs: FieldSpecOne[_,T]) = FieldSpecOneNode(fs) + implicit def nodeOps2Node[T](ops: NodeOps[T]) = ops.leftOperand + implicit def const2Node[T](x: T) = ConstNode(x) + implicit def arbitraryPredicate2Node[T](p: Predicate[T]) = PredicateNode(p) + + //a few lifts to traits that define operations one can apply to given Nodes. + implicit def fieldSpecOne2BooleanNodeOps(fs: FieldSpecOne[_,Boolean]) = new BooleanNodeOps { + val leftOperand = FieldSpecOneNode(fs) + } + implicit def fieldSpecOne2AllNodeOps[T](fs: FieldSpecOne[_,T]) = new AllNodeOps[T] { + val leftOperand = FieldSpecOneNode(fs) + } + + implicit def booleanConst2BooleanNodeOps(x: Boolean) = new BooleanNodeOps { + val leftOperand = ConstNode(x) + } + implicit def const2AllNodeOps[T](x: T) = new AllNodeOps[T] { + val leftOperand = ConstNode(x) + } + + implicit def predicate2BooleanNodeOps(p: Predicate[_]) = new BooleanNodeOps { + val leftOperand = PredicateNode(p) + } + +} diff --git a/src/test/scala/com/google/gimd/query/QueryASTTestCase.scala b/src/test/scala/com/google/gimd/query/QueryASTTestCase.scala new file mode 100644 index 0000000..68047a1 --- /dev/null +++ b/src/test/scala/com/google/gimd/query/QueryASTTestCase.scala @@ -0,0 +1,87 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gimd.query + +import com.google.gimd._ +import com.google.gimd.UserType._ +import com.google.gimd.query.AllNodeOps._ +import com.google.gimd.query.BooleanNodeOps._ +import com.google.gimd.query.Query._ +import org.junit.Test +import org.junit.Assert._ + +class QueryASTTestCase { + + case class TreeNode(id: Int, name: String) + object TreeNodes extends UserType[TreeNode] { + val id = FieldSpecOne("id", IntField, _.id) + val name = FieldSpecOne("name", StringField, _.name) + def fields = id :: name + override def children = Seq(new NestedMember("node", TreeNodes)) + def toUserObject(m: Message) = new TreeNode(id(m), name(m)) + } + + @Test + def equalityOperators = { + val q1 = TreeNodes where { _.name is "Joe" } + val q2 = TreeNodes where { _.name === "Joe" } + val expected = Is(FieldSpecOneNode(TreeNodes.name), ConstNode("Joe")) :: Nil + assertEquals(expected, q1.cond) + assertEquals(expected, q2.cond) + } + + @Test + def nonEqualityOperators { + val q1 = TreeNodes where { _.name !== "Joe" } + val q2 = TreeNodes where { _.name isNot "Joe" } + val expected = Not(Is(FieldSpecOneNode(TreeNodes.name), ConstNode("Joe"))) :: Nil + assertEquals(expected, q1.cond) + assertEquals(expected, q2.cond) + } + + @Test + def booleanOperators { + val left = TreeNodes.name is "Joe" + val right = ConstNode(false) + val qAnd = TreeNodes where { x => (x.name is "Joe") && false } + val qOr = TreeNodes where { x => (x.name is "Joe") || false } + val qNot = TreeNodes where { x => !(x.name is "Joe") } + assertEquals(And(left, right) :: Nil, qAnd.cond) + assertEquals(Or(left, right) :: Nil, qOr.cond) + assertEquals(Not(left) :: Nil, qNot.cond) + } + + @Test + def orderingOperators { + val q1 = TreeNodes where { _.name < "Joe" } + val q2 = TreeNodes where { _.name <= "Joe" } + val q3 = TreeNodes where { _.name > "Joe" } + val q4 = TreeNodes where { _.name >= "Joe" } + val lt = Relational("<", FieldSpecOneNode(TreeNodes.name), ConstNode("Joe")) + val is = Is(FieldSpecOneNode(TreeNodes.name), ConstNode("Joe")) + assertEquals(lt :: Nil, q1.cond) + assertEquals(Or(lt, is) :: Nil, q2.cond) + assertEquals(And(Not(lt), Not(is)) :: Nil, q3.cond) + assertEquals(Not(lt) :: Nil, q4.cond) + } + + @Test + def predicateNode { + val p = Predicate[TreeNode](_.name == "Joe") + val q = TreeNodes where { x => p && true } + assertEquals(And(PredicateNode(p), ConstNode(true)) :: Nil, q.cond) + } + +} |