Yesterday I was looking around on the inter-webs for a Scala project that was “iBatis” like, but more Scala oriented.  I found some cool stuff (ScalaQL, and scala-sql-dsl for example) but nothing quite what I was looking for.

I kind of chewed on what exactly it was that I was looking for and this is my 10k/foot feature list:

  • Ability to define the underlying data store in a DSL that can be understood by Scala
  • Ability to map Scala classes to JDBC results by binding to artifacts of the data store

This is more or less what iBatis accomplishes with xml (now annotations) and a bunch of reflection.  I’d like to see it done without XML and minimal reflection.

That sent me down the rabbit hole on how exactly one goes about defining a DSL in scala.  I re-read chapter 31 in Programming in Scala.  I also read chapter 11 in Programming Scala.  Then I took a stab at writing a parser combinator for a very, very small subset of the “CREATE TABLE” DDL syntax for SQL.

import scala.util.parsing.combinator._

class TableDdlParser extends JavaTokenParsers {

def tables: Parser[Map[String, Any]] = rep(table) ^^ { Map() ++ _ }

def table: Parser[(String,Any)] =
("TABLE" ~ tableName ~ columns
^^ { case "TABLE" ~ tableName ~ tableContents => (tableName,tableContents) })

def tableName: Parser[String] = ident ^^ { case ident => ident }

def columns: Parser[Map[String, Any]] = "("~> repsep(column, ",") <~")" ^^ { Map() ++ _ }

def column: Parser[(String,Any)] =
columnName ~ dataType ^^ { case columnName ~ dataType => (columnName,dataType) }

def columnName: Parser[String] = ident ^^ { case ident => ident }

def dataType: Parser[Any] = "VARCHAR" | "INTEGER"

}

object TableDdlParserRunner extends TableDdlParser {

def main(args: Array[String]) {
val input =
"""TABLE person (first_name VARCHAR, last_name VARCHAR, age INTEGER)
TABLE place (city VARCHAR, state VARCHAR)"""
println(parseAll(tables,input))
}

}


Here is the output



[2.51] parsed: 
Map(person -> Map(first_name -> VARCHAR,
last_name -> VARCHAR,
age -> INTEGER),
place -> Map(city -> VARCHAR,
state -> VARCHAR))


This experiment only supports two data types (VARCHAR and INTEGER), without any of the other metadata that is required for those types.  I just wanted a feel for how hard it would be to write and didn’t want to get bogged down in the details.  It turned out to be quite a bit easier than I thought it would be.



Next up is to figure out the best way to map this DDL information to a scala class (If I don’t get distracted by something else first).