Introduction to Quill

23
1 COMPILE-TIME LANGUAGE INTEGRATED QUERIES FOR SCALA Mateusz Bilski

Transcript of Introduction to Quill

Page 1: Introduction to Quill

1

COMPILE-TIME LANGUAGEINTEGRATED QUERIES FOR SCALA

Mateusz Bilski

Page 2: Introduction to Quill

2

SCALA SELLING POINTStype safety and inferencecompiler works for youno need to unit test everything

Page 3: Introduction to Quill

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

Page 4: Introduction to Quill

3

4

WHAT IS QUILL?Quoted Domain Specific LanguageSlick alternativeAsynchronous SQL clientCassandra supportBoilerplate free mappingCompile time query generation and validation

Page 5: Introduction to Quill

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

Page 6: Introduction to Quill

6

AUTHORFlavio W. Brasil

So�ware engineer at Twitter

Created and clump activate

Page 7: Introduction to Quill

7

ENOUGH. LET'S SEE SOME CODE!

Page 8: Introduction to Quill

8

build.sbt

libraryDependencies += "io.getquill" %% "quill-async" % "0.6.0"

application.conf

db.default { host = "localhost" port = 3306 user = "root" database = "quill" }

Page 9: Introduction to 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 );

Page 10: Introduction to Quill

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)))

Page 11: Introduction to Quill

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))

Page 12: Introduction to Quill

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) }

Page 13: Introduction to Quill

12

13

Dynamic queries

val r = scala.util.Random

def dynamic: Quoted[Query[User]] = quote { table.filter(_.isActive == r.nextBoolean()) }

Page 14: Introduction to Quill

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

Page 15: Introduction to Quill

14

15

QUERY PROBING DEMO

Page 16: Introduction to Quill

16

HOW DOES THE QUERY GENERATION WORK?

Page 17: Introduction to Quill

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) } }

Page 18: Introduction to Quill

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))))))

Page 19: Introduction to Quill

18

19

Normalisation

Page 20: Introduction to Quill

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;

Page 21: Introduction to Quill

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

Page 22: Introduction to Quill

22

PROBLEMSusage of whitebox macroslack of batch operationsunstable query probingimplemented by one guyno production usage reported yet

Page 23: Introduction to Quill

23

THE ENDgithub.com/play-quill-jdbc