Introduction to Quill

Post on 16-Apr-2017

270 views 2 download

Transcript of Introduction to Quill

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

23

THE ENDgithub.com/play-quill-jdbc