ScalaQuery

Před nějakou dobou jsem porovnával for expression ze Scaly a LINQ z C# a došel jsem k závěru, že for comprehension umí skoro všechno, co LINQ. Jednoduchému for
conprehenshion zcela nepřekvapivě chybí konstrukce pro pohodlné provádění
operací jako group by
, order by
a samozřejmě obdoba LINQ to SQL, což je kompletní ORM.
For comprehension je konstrukce pro kompozici monád, takže není principiálně nemožné, vytvořit a skládat takové monády, které budou vykonávat všechny potřebné operace.
ScalaQuery je knihovna pro typově bezpečný přístup k relační databázi, která šikovně využívá právě for comprehension a je plně srovnatelná s LINQ.
Rozdíly oproti LINQ to SQL:
- ScalaQuery nenačítá definice tabulek z anotací entit, ale tabulky si musíme definovat jako samostatné objekty.
- Pro sestavování dotazů nepoužívá „dekompozici parse tree.(kterou Scala mimo jiné také umí)“ jako C# LINQ, ale polymorphic embedding.
- Stejně jako SQL pracuje s uspořádanými n-ticemi (typy TupleN), což může být poněkud neohrabané, když máme tabulky s velkým množstvím sloupců.
(Většina následujících ukázek pochází z wiki projektu)
Definice tabulek
object Users extends Table[(Int, String, Option[String])]("users") { def id = column[Int] ("id", O NotNull) def first = column[String] ("first", O Default "NFN", O DBType "varchar(64)") def last = column[Option[String]]("last") def * = id ~ first ~ last } object Orders extends Table[(Int, Int, String, Boolean, Option[Boolean])]("orders") { def userID = column[Int]("userID", O NotNull) def orderID = column[Int]("orderID", O NotNull) def product = column[String]("product") def shipped = column[Boolean]("shipped", O Default false, O NotNull) def rebate = column[Option[Boolean]]("rebate", O Default Some(false)) def * = userID ~ orderID ~ product ~ shipped ~ rebate def user = foreignKey("user_fk", userID, Users)(_.id) }
Jednoduché selecty
for (u <- Users) yield u.*
SELECT * FROM users
for { u <- Users if u.first === "Stefan" } yield u.id ~ u.last
SELECT id, last FROM users WHERE first = 'Stefan'
Count
(for (u <- Users) yield u.*).count // SELECT count(*) FROM users for (u <- Users) yield u.first.count // SELECT count(first) FROM users for (u <- Users) yield u.first.countDistinct // SELECT count(distinct first) FROM users
Implicitní join se vytvoří zřetězením více generátorů
for { u <- Users o <- Orders if o.userID is u.id } yield u.first ~ o.orderID
SELECT u.first, u.orderID FROM users u, orders o WHERE u.id = o.userID
Explicitní join
for { Join(u, o) <- Users innerJoin Orders on (_.id is _.userID) } yield u.first ~ o.orderID
SELECT u.first, u.orderID FROM users u JOIN orders o ON u.id = o.userID
Join přes cizí klíč
for { o <- Orders if o.shipped === true u <- o.user } yield u.first ~ o.product
Explicitní outer join
for { Join(u, o) <- Users leftJoin Orders on (_.id is _.userID) } yield u.first ~ o.orderID.?
Group by
for { o <- Orders u <- o.user _ <- Query groupBy u.id } yield u.first.min.get ~ o.orderID.count
PS: Matin Odersky se na Scala Exchange 2011 letmo zmínil, že knihovna pro přístup k databázím a externím datovým zdrojům podobná ScalaQuery se v rámci projektu slick chystá přímo do nějaké budoucí verze Scaly
PS2: Krátce po publikování článku jsem se dozvěděl o Scala Integrated Query – ScalaQuery + nějaké zajímavé funkce navíc. (via @ijuma)