I finally had some free time to spend on Gimd and it's query DSL.
What I've come up with just a few lines of code is following result:
case class TreeNode(id: Int, name: String, select: String)
object TreeNodes extends UserType[TreeNode] {
val id = FieldSpecOne("id", IntField, _.id)
val name = FieldSpecOne("name", StringField, _.name)
val select = FieldSpecOne("select", StringField, _.select)
def fields = id :: name :: select
override def children = Seq(new NestedMember("node", TreeNodes))
def toUserObject(m: Message) = new TreeNode(id(m), name(m), select(m))
}
def whereClause {
def f(x: Int) = x * 2
val q1 = TreeNodes where { x => (x.select === "true") && x.id > 10
} where { _.name is "Joe" }
val q2 = TreeNodes where { x => Predicate[TreeNode](y => f(y.id) == 4) }
println(q1.cond mkString "\n")
println("--------------------------------")
println(q2.cond mkString "\n")
}
Which prints the following:
Is(FieldSpecNode(FieldSpecOne(name,<function>,<function>)),ConstNode(Joe))
And(Is(FieldSpecNode(FieldSpecOne(select,<function>,<function>)),ConstNode(true)),Relational(>,FieldSpecNode(FieldSpecOne(id,<function>,<function>)),ConstNode(10)))
--------------------------------
PredicateNode(com.google.gimd.query.Predicate$$anon$1@79f6f296)
Just to clarify a few things:
1. Both q1 and q2 are of type Query[TreeNodes.type]
2. TreeNodes object is lifted to Query by implicit conversion
3. Both === and `is` are equivalent operators. I couldn't use ==
because it's defined on Any and cannot be overloaded.
3. You can put arbitrary of where clauses (Query.cond is a list).
4. Everything is type-safe. For example, there is no way to apply
arbitrary function in `where` clause without annotating it as being a
Predicate as shown in q2.
All this is implemented in around 150 lines of code and is pretty
straight-forward. I think I can finish DSL syntax and it's AST
representation soon.
I'll try to submit patches in following days. Next task would be to
make use of this work by incorporating Lucene. I'll start another
thread on this.
--
Best regards,
Grzegorz
Yay, that's more than I can say...
> What I've come up with just a few lines of code is following result:
...
> def whereClause {
> def f(x: Int) = x * 2
> val q1 = TreeNodes where { x => (x.select === "true") && x.id > 10
> } where { _.name is "Joe" }
> val q2 = TreeNodes where { x => Predicate[TreeNode](y => f(y.id) == 4) }
Nice.
> Just to clarify a few things:
> 1. Both q1 and q2 are of type Query[TreeNodes.type]
> 2. TreeNodes object is lifted to Query by implicit conversion
> 3. Both === and `is` are equivalent operators. I couldn't use ==
> because it's defined on Any and cannot be overloaded.
Expected. === and is are fine for operators.
> 3. You can put arbitrary of where clauses (Query.cond is a list).
> 4. Everything is type-safe. For example, there is no way to apply
> arbitrary function in `where` clause without annotating it as being a
> Predicate as shown in q2.
I assume this means == (double equals which isn't overridden but
inherited from Any) can't be used in a query without first doing the
explicit cast to Predicate, right? Which gives us error checking
that you used the proper compare operator.
Exactly. What `where` accepts is U => Node[Boolean], where U is
defined U <: UserType[_] (subtype of UserType thus just user-provided
concrete UserType definition).
This way we have double check for type-safety. First of all the result
must be Node[Boolean] and not Boolean. In order to create
Node[Boolean] you need to use operators from well defined set (=== is
one example of them). Another way we ensure that you cannot trick our
system is that function goes from U which describes concrete user
class but not from that class.
For example in explicit definition of predicate Predicate[TreeNode](y
=> f(y.id) == 4) the predicate has type TreeNode => Boolean and not
TreeNodes.
As a consequence of the fact that we give two hints to compiler what
is expected instead of one hint, we get, in most situations, really
sane compiler errors that one can easily use to correct her code.
Enough of talking. I'll prepare some early version of my first patch
so you can actually see where I'm heading and give some early
feedback.
--
Grzegorz
Here it comes:
https://review.source.android.com/14167
I'd be grateful for your opinion on this patch. I'm especially
interested in how readable this code is.
--
Best regards,
Grzegorz Kossakowski