summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Kossakowski <grzegorz.kossakowski@gmail.com>2010-04-05 19:43:16 +0200
committerGrzegorz Kossakowski <grzegorz.kossakowski@gmail.com>2010-04-05 19:43:16 +0200
commit5aaef587ec746145959482568c83172455a19ac9 (patch)
treeeac2230ad7939eda9e0698c8321602a0fdc7ff2d
parent6950ff0945e7037686ca855380ac9243cd972bba (diff)
downloadgimd-5aaef587ec746145959482568c83172455a19ac9.tar.gz
Add initial code for the Query DSL.
This commit adds the code defines basic AST nodes and operations that allow one to construct ASTs. Both form basic, type-safe query API for Gimd. Examples of the API usage can be found in tests that are provided. This code adds support for FieldSpecOne variant of FieldSpec. It's left to implement handling of optional values and lists which will support different set of operations. Change-Id: Ie7886ee012c4286a15289b14aa4b09be060c7bf3 Signed-off-by: Grzegorz Kossakowski <grzegorz.kossakowski@gmail.com>
-rw-r--r--src/main/scala/com/google/gimd/query/Node.scala44
-rw-r--r--src/main/scala/com/google/gimd/query/NodeOps.scala75
-rw-r--r--src/main/scala/com/google/gimd/query/Query.scala61
-rw-r--r--src/test/scala/com/google/gimd/query/QueryASTTestCase.scala87
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)
+ }
+
+}