Greetings,
I'm an intermediate Scala developer with just enough knowledge to get himself intro trouble thinking big thoughts and trying (perhaps) silly things.
Recently I found myself frustrated by the lack of typesafety in Datastax Cassandra driver. There are Scala alternatives that seek to alleviate this (Phantom comes to mind:
https://github.com/websudos/phantom) but generally they lag behind the "latest and greatest" features added by Datastax into the CQL standard.
So the basic structure for making a (repeatable) Cassandra query in the Datastax driver is designed to look very much like JDBC - generally something like this (ignoring for now the reactive/async model which is preferable and that preparing a statement multiple times is an anti-pattern):
val statement: PreparedStatement = session.prepare("INSERT INTO TestKeyspace.TestTable (a, b, c) VALUES (?, ?, ?)")
val result: ResultSet = session.execute(statement.bind(1, List(1, 2, 3).toJava, "hello world"))
The 'bind(...)' varargs call on the PreparedStatement must disaggregate up to the highest type (since the Datastax driver is Java) so it's just a varargs of Java "Object" types.
It is possible to inspect a constructed statement and get a list of Class types back representing the class types for each bound argument. I assume then, that for a PreparedStatement such as the one above, target type information may be obtained at compile time.
So this brings us to Shapeless and HList. I wondered to myself - "would it be possible to define a compile-time macro to in-line code to force typesafety when binding a statement?"
This lead me to a basic structure for my target macro that looks something like this:
import com.datastax.driver.core.{BoundStatement, PreparedStatement}
import shapeless.HList
import scala.reflect.macros._
import scala.language.experimental.macros
/**
* The idea is to try to build a Scala macro that will leverage Shapeless HList to provide a typesafe compile-time
* alternative to the very un-typesafe Datastax cassandra driver PreparedStatement implementation.
*
* I'm going to try to achieve this by passing the macro a PreparedStatement and then trying to generate code
* that returns a first-class function mapping an instance of the provided HList subtype to a BoundStatement
* (by iterating over the HList and using it as the varargs to the .bind(...) method on PreparedStatement).
*
* An example may serve better... so:
*
* Target Usage:
*
* cqlsh> CREATE TABLE TestKeyspace.TestTable (a int, b list<text>, c double)
*
* import scala.collection.JavaConverters._
* import scala.collection.JavaConversions._
* val typesafe = CQL_TYPESAFE_FUNC(session.prepare("""INSERT INTO TestKeyspace.TestTable (a, b, c) VALUES (?, ?, ?)"""))
* val result: ResultSet = session.execute(typesafe(new java.lang.Integer(1) :: List("a", "b", "c").asJava :: new java.lang.Double(3.0) :: HNil))
*
* Ideally typesafety is checked at compile time, an appropriate HList type is generated based on inspecting the
* provided PreparedStatement instance, and the function returned by the macro then requires an HList of that type
* to produce the corresponding BoundStatement. This would eliminate the constant runtime errors caused by
* forgetting to call ".toJava" everywhere on the various types - those errors would be moved to compile-time issues.
*/
object CQL_TYPESAFE_FUNC {
def apply(statement: PreparedStatement): _ <: HList => BoundStatement = macro impl
def impl(c: whitebox.Context)(statement: c.Expr[PreparedStatement]): c.Expr[_ <: HList => BoundStatement] = {
import c.universe._
//TODO: ??? (here be dragons!)
}
}
And that's where I get stuck. My lack of further knowledge about how HList and Macros work gets the better of me.
So - my question is two-fold:
1. Given the above information, do you believe it may be possible to introduce a macro to provide compile-time type safety on 'bind(...)' through a technique like the one sketched out above - i.e. by producing a function which requires a subtype of HList to produce a BoundStatement? (or have I grossly misunderstood what's possible with Scala macros?)
2. What resources would you recommend for me to get smarter on how I might accomplish what I've set out to attempt? (provided, of course, that the answer to no. 1 above is in the affirmative)
I've done some google-ing around but the best I've come up with thus-far is the scala documentation itself and a whitepaper on Quasiquotes.
Thanks ahead of time for any insight you may be able to provide.
Kind Regards,
Andy