Post on 16-Apr-2017
1
COMPILE-TIME LANGUAGEINTEGRATED QUERIES FOR SCALA
Mateusz Bilski
2
SCALA SELLING POINTStype safety and inferencecompiler works for youno need to unit test everything
3
SQL
val db = Database.forConfig("database") db.run(sql"SELECT * FROM users WHERE id=${id}".as[User].headOption)
DSL
val users = TableQuery[User] db.run(users.filter(_.id == 1).result.headOption)
Lists
val users = List(User(1), User(2), User(3)) users.filter(_.id == 1).headOption
3
4
WHAT IS QUILL?Quoted Domain Specific LanguageSlick alternativeAsynchronous SQL clientCassandra supportBoilerplate free mappingCompile time query generation and validation
5
BACKGROUNDInspired by
(2013) white paper.First commit at Jul 19, 2015.Current release is 0.7.0.Stable version planned for Summer 2016.
A Practical Theory of Language-IntegratedQuery
6
AUTHORFlavio W. Brasil
So�ware engineer at Twitter
Created and clump activate
7
ENOUGH. LET'S SEE SOME CODE!
8
build.sbt
libraryDependencies += "io.getquill" %% "quill-async" % "0.6.0"
application.conf
db.default { host = "localhost" port = 3306 user = "root" database = "quill" }
8
9
evolutions.sql
CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, is_active BOOLEAN NOT NULL );
CREATE TABLE devices ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, user_id INT NOT NULL );
9
10
Source definition
lazy val db = source { new MysqlAsyncSourceConfig[SnakeCase]("db.default") }
Model definition
case class User(id: Long, name: String, isActive: Boolean) case class Device(id: Long, name: String, userId: Long)
Schema
val Users = quote(query[User].schema(_.generated(_.id))) val Devices = quote(query[Device].schema(_.generated(_.id)))
10
11
Queriesdef byId(id: Long) = quote { Users.filter(_.id == lift(id)) }
def byIdWithDevices(id: Long) = quote { table .leftJoin(DevicesRepository.table) .on((u, d) => u.id == d.userId) .filter(_._1.id == lift(id)) }
Executiondef find(id: Long): Future[Option[User]] = db.run(byId(id)).map(_.headOption)
def findWithDevices(id: Long): Future[List[(User, Option[Device])]] = db.run(byIdWithDevices(id))
11
12
CRUD
def create(user: User): Future[User] = { db.run(Users.insert)(List(user)).map { newId => user.copy(id = newId.head) } }
def update(user: User): Future[List[Long]] = { db.run(byId(user.id).update)(List(user)) }
def delete(user: User): Future[List[User]] = { db.run(byId(user.id).delete) }
12
13
Dynamic queries
val r = scala.util.Random
def dynamic: Quoted[Query[User]] = quote { table.filter(_.isActive == r.nextBoolean()) }
13
14
Logs
Service.scala:13: SELECT x3.id, x3.name, x3.is_active FROM users x3
WHERE x3.id = ?
Service.scala:16: SELECT u.id, u.name, u.is_active, d.id, d.name, d.user_id
FROM users u LEFT JOIN devices d ON u.id = d.user_id
WHERE u.id = ?
Service.scala:19: INSERT INTO users (name,is_active) VALUES (?, ?)
Service.scala:25: UPDATE users SET id = ?, name = ?, is_active = ?
WHERE id = ?
Service.scala:29: DELETE FROM users WHERE id = ?
Service.scala:33: Dynamic query
14
15
QUERY PROBING DEMO
16
HOW DOES THE QUERY GENERATION WORK?
17
Query
quote { for { c <- couples w <- people m <- people if (c.her == w.name && c.him == m.name && w.age > m.age) } yield { (w.name, w.age - m.age) } }
17
18
AST
FlatMap(Entity(Couple, None, List()), Ident(c), FlatMap(Entity(Person, None, List()), Ident(w), Map(Filter(Entity(Person, None, List()), Ident(m),BinaryOperation(BinaryOperation(BinaryOperation( Property(Ident(c), her), ==, Property(Ident(w), name)), &&, BinaryOperation(Property(Ident(c), him), ==, Property(Ident(m), name))), &&, BinaryOperation( Property(Ident(h), age), >, Property(Ident(m), age)))), Ident(m), Tuple(List(Property(Ident(m), name), BinaryOperation(Property(Ident(w), age), -, Property(Ident(m), age))))))
18
19
Normalisation
19
20
AST + Normalisation = SQL
SELECT w.name, w.age - m.age FROM couples c, people w, people m WHERE c.her = w.name AND c.him = m.name AND w.age > m.age;
20
21
QUILL VS SLICKQUILL
Language Integrated QueryQuoted DSLMapping using simple case classesCompile time query generationFully asynchronous non-blocking database accessQuery extensibility
SLICK
Functional Relational MappingEmbedded DSLExplicit type definitionRuntime query generationAsynchronous wrapper on top of jdbcRaw statements
22
PROBLEMSusage of whitebox macroslack of batch operationsunstable query probingimplemented by one guyno production usage reported yet