I know of four 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.
ScalaQuery
The first one is ScalaQuery. 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")
}
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)
}
}
}
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.