Problem And Question
I would like to use Scala to persist data to a relational database, so what I am looking for are examples of CRUD operations using Scala.
I would like to code on a lower level of abstraction than an ORM like Hibernate/Toplink (read:JDBC), but between us, I would like to see examples of all types.
Best Solution And Answer
EDIT: There’s now a pretty good wiki about Scala libraries and frameworks here.
I know of five usable non-ORM database libraries for Scala. There’s also one ORM which I mention below because it doesn’t hide SQL, which might just make it a good fit.
Slick (previsously named Scalaquery)
The first one is Slick. It is the most mature one, and it tries to make queries use the same for-comprehension as Scala collections do. As an example of syntax style (which might be slightly out of date):
import java.lang.Integer import com.novocode.squery._ import com.novocode.squery.Implicit._ import com.novocode.squery.session._ import com.novocode.squery.session.SessionFactory._ // Define table: object Users extends Table[(Integer, String, String)]("users") { def id = intColumn("id", O.AutoInc, O.NotNull) def first = stringColumn("first") def last = stringColumn("last") def * = id ~ first ~ last } // Basic usage val sf = new DriverManagerSessionFactory("org.h2.Driver", "jdbc:h2:mem:test1") sf withSession { // Prepare a simple query val q1 = for(u <- Users) yield u // Print SQL statement to be executed: println(q1.selectStatement) // displays SELECT t1.id,t1.first,t1.last FROM users t1 // Print query result: for(t <- q1) println("User tuple: "+t) // Query statements can also be used with updates: val q = for(u <- Users if u.id is 42) yield u.first ~ u.last q.update("foo", "bar") }
As the project was renamed recently some resources are still under scalaquery name (website, groups). Slick will soon be included in Typesafe stack which can be seen as a proof of its maturity.
Querulous
The second one is Querulous, which is a open source project from Twitter. This one gives you direct access to SQL, while dealing with a bunch of jdbc annoyances. Here’s a simple example:
import com.twitter.querulous.evaluator.QueryEvaluator val queryEvaluator = QueryEvaluator("host", "username", "password") val users = queryEvaluator.select("SELECT * FROM users WHERE id IN (?) OR name = ?", List(1,2,3), "Jacques") { row => new User(row.getInt("id"), row.getString("name")) } queryEvaluator.execute("INSERT INTO users VALUES (?, ?)", 1, "Jacques") queryEvaluator.transaction { transaction => transaction.select("SELECT ... FOR UPDATE", ...) transaction.execute("INSERT INTO users VALUES (?, ?)", 1, "Jacques") transaction.execute("INSERT INTO users VALUES (?, ?)", 2, "Luc") }
Squeryl
The third one is Squeryl. Style-wise, it sits midway between ScalaQuery — which hides SQL behind Scala comprehensions as much as possible — and Querulous — which uses SQL strings directly.
Squeryl provides a SQL-like DSL, which gives you type safety and give you a strong likelyhood that the statements won’t fail at run-time if they compile at all. Again, a simple example:
// Defining tables and a schema: import org.squeryl.PrimitiveTypeMode._ class Author(var id: Long, var firstName: String, var lastName: String) class Book(var id: Long, var title: String, @Column("AUTHOR_ID") // the default 'exact match' policy can be overriden var authorId: Long, var coAuthorId: Option[Long]) { def this() = this(0,"",0,Some(0L)) } object Library extends Schema { //When the table name doesn't match the class name, it is specified here : val authors = table[Author]("AUTHORS") val books = table[Book] } // Basic usage Class.forName("org.postgresql.Driver"); val session = Session.create( java.sql.DriverManager.getConnection("jdbc:postgresql://localhost:5432/squeryl", "squeryl", "squeryl"), new PostgreSqlAdapter ) //Squeryl database interaction must be done with a using block : import Library._ using(session) { books.insert(new Author(1, "Michel","Folco")) val a = from(authors)(a=> where(a.lastName === "Folco") select(a)) }
O/R Broker
The fourth is O/R Broker, which, despite the name, is not an ORM. Classes can be designed in any way desired. No interfaces/traits to implement, no conventions to uphold, no annotations needed.
case class Song(id: Option[Long], title: String, seconds: Short) case class Album(id: Option[Long], title: String, year: Short, songs: IndexedSeq[Song]) case class Artist(id: Option[Long], name: String, albums: Set[Album])
Extractors are declarative, written in Scala. Can be reused in other queries that fit the expectation of the extractor.
object SongExtractor extends JoinExtractor[Song] { val key = Set("SONG_ID") def extract(row: Row, join: Join) = { new Song( row.bigInt("SONG_ID"), row.string("TITLE").get, row.smallInt("DURATION_SECONDS").get ) } } object AlbumExtractor extends JoinExtractor[Album] { val key = Set("ALBUM_ID") def extract(row: Row, join: Join) = { new Album( row.bigInt("ALBUM_ID"), row.string("TITLE").get, row.smallInt("YEAR_ISSUED").get, join.extractSeq(SongExtractor, Map("TITLE"->"SONG_TITLE")) ) } } object ArtistExtractor extends JoinExtractor[Artist] { val key = Set("ARTIST_ID") def extract(row: Row, join: Join) = { new Artist( row.bigInt("ARTIST_ID"), row.string("NAME"), join.extractSeq(AlbumExtractor) ) } }
One could then use that like this:
val ds: javax.sql.DataSource = ... val builder = new SQLFileBuilder(ds, new java.io.File("sql/")) val broker = builder.build() // Print all artists with their albums (if any) val artists = broker.readOnly() { session => session.selectAll[Artist]('selectArtist) // ' I wish they could fix the Scala Symbol formatting } for (ar <- artists) { println(a.name) if (ar.albums.isEmpty) println("\t<No albums>") else for (al <- ar.albums) { println("\t" + al.title) for (s <- al.songs) { println("\t\t" + (al.songs.indexOf(s)+1) + ". " + s.title) } } }
Anorm
Anorm comes from Play Framework, and I don’t know if it can be used stand alone or not. Basically, it ditches mappings and DSL completely, giving you direct access to SQL. A simple query may look like this:
// Create an SQL query val selectCountries = SQL("Select * from Country") // Transform the resulting Stream[Row] as a List[(String,String)] val countries = selectCountries().map(row => row[String]("code") -> row[String]("name") ).toList
It also supports pattern matching for row extraction:
val countries = SQL("Select name,population from Country")().collect { case Row("France", _) => France() case Row(name:String, pop:Int) if(pop > 1000000) => BigCountry(name) case Row(name:String, _) => SmallCountry(name) }
Binding variables in queries uses this syntax:
SQL( """ select * from Country c join CountryLanguage l on l.CountryCode = c.Code where c.code = {countryCode}; """ ).on("countryCode" -> "FRA")
And it also has support for use of parse combinators to translate queries or even table schemas into data structures. You can either define the parser yourself, or use some default conventions (like a case class mapping field names to column names) and let it do the work for you.
Circumflex ORM
Finally, there’s Circumflex ORM. I’m copying here a few examples from their site:
class Category extends Record[Category] { val id = field(Category.id) val name = field(Category.name) val books = oneToMany(Book.category) // allows navigating between associations transparently } object Category extends Table[Category] with LongIdPK[Category] { val name = stringColumn("name") // creates a column .notNull // creates NOT NULL constraint .unique // creates UNIQUE constraint .validateNotEmpty // adds NotEmpty validation .validatePattern("^[a-zA-Z]{1,8}$") // adds Pattern validation } class Book extends Record[Book] { val id = field(Book.id) val title = field(Book.title) val category = manyToOne(Book.category) } object Book extends Table[Book] with LongIdPK[Book] { val title = stringColumn("title") .notNull .validateNotEmpty val category = longColumn("category_id") .references(Category) // creates an association with Category .onDeleteSetNull // specifies a foreign-key action .onUpdateCascade } new DDLExport(Category, Book).create // creates database schema // find category by id val c = Category.get(2l) // find all books val allBooks = Book.all // find books for category val cBooks = c.get.books // find books by title Book.criteria.add("title" like "a%").list select() .from(Category as "c" join (Book as "b"), Category as "c1") .where("c1.name" like "a%") .addOrder(asc("c.name")) .list select(count("b.id"), "c.name").from(Category as "c" join (Book as "b")).list
If I missed any existing project, just drop a comment and I’ll add them to this answer. Don’t bother with blogs, papers, wikis or the like, though.
jOOQ
Although jOOQ is currently a mostly Java-based solution, it still renders SQL quite nicely in Scala thanks to Scala’s capabilities of omitting dots and parentheses for some method calls. The following example was taken from an authoritative blog post. Other examples here.
// Fetch book titles and their respective authors into // a result, and print the result to the console. val result = (create select ( BOOK.TITLE as "book title", AUTHOR.FIRST_NAME as "author's first name", AUTHOR.LAST_NAME as "author's last name") from AUTHOR join BOOK on (AUTHOR.ID equal BOOK.AUTHOR_ID) where (AUTHOR.ID in (1, 2, 3)) orderBy (AUTHOR.LAST_NAME asc) fetch)
and also
// Iterate over authors and the number of books they've written // Print each value to the console for (r <- (create select (AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, count) from AUTHOR join BOOK on (AUTHOR.ID equal BOOK.AUTHOR_ID) where (AUTHOR.ID in (1, 2, 3)) groupBy (AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) orderBy (AUTHOR.LAST_NAME asc) fetch)) { print(r.getValue(AUTHOR.FIRST_NAME)) print(" ") print(r.getValue(AUTHOR.LAST_NAME)) print(" wrote ") print(r.getValue(count)) println(" books ") }