IMPORTANT: This is the documentation for the latest SNAPSHOT version. Please refer to the website at http://getquill.io for the lastest release's documentation.
Compile-time Language Integrated Query for Scala
Quill provides a Quoted Domain Specific Language (QDSL) to express queries in Scala and execute them in a target language. The library's core is designed to support multiple target languages, currently featuring specializations for Structured Query Language (SQL) and Cassandra Query Language (CQL).
- Boilerplate-free mapping: The database schema is mapped using simple case classes.
- Quoted DSL: Queries are defined inside a
quoteblock. Quill parses each quoted block of code (quotation) at compile time and translates them to an internal Abstract Syntax Tree (AST) - Compile-time query generation: The
db.runcall reads the quotation's AST and translates it to the target language at compile time, emitting the query string as a compilation message. As the query string is known at compile time, the runtime overhead is very low and similar to using the database driver directly. - Compile-time query validation: If configured, the query is verified against the database at compile time and the compilation fails if it is not valid. The query validation does not alter the database state.
The QDSL allows the user to write plain Scala code, leveraging scala's syntax and type system. Quotations are created using the quote method and can contain any excerpt of code that uses supported operations. To create quotations, first import quote and some other auxiliary methods:
importio.getquill._A quotation can be a simple value:
valpi= quote(3.14159)And be used within another quotation:
caseclassCircle(radius: Float)
valareas= quote {
query[Circle].map(c => pi * c.radius * c.radius)
}Quotations can also contain high-order functions and inline values:
valarea= quote {
(c: Circle) => {
valr2= c.radius * c.radius
pi * r2
}
}valareas= quote {
query[Circle].map(c => area(c))
}Quotations can contain values defined outside of the quotation:
valpi=3.14159valareas= quote {
query[Circle].map(c => pi * c.radius * c.radius)
}Quill's normalization engine applies reduction steps before translating the quotation to the target language. The correspondent normalized quotation for both versions of the areas query is:
valareas= quote {
query[Circle].map(c =>3.14159* c.radius * c.radius)
}Scala doesn't have support for high-order functions with type parameters. Quill supports anonymous classes with an apply method for this purpose:
valexistsAny= quote {
new {
defapply[T](xs: Query[T])(p: T=>Boolean) =
xs.filter(p(_)).nonEmpty
}
}
valq= quote {
query[Circle].filter { c1 =>
existsAny(query[Circle])(c2 => c2.radius > c1.radius)
}
}Sources represent the database and provide an execution interface for queries. Quill provides mirror sources for test purposes. Please refer to sources for information on how to create normal sources.
Instead of running the query, mirror sources return a structure with the information that would be used to run the query. There are three mirror source configurations:
io.getquill.MirrorSourceConfig: Mirrors the quotation ASTio.getquill.SqlMirrorSourceConfig: Mirrors the SQL queryio.getquill.CassandraMirrorSourceConfig: Mirrors the CQL query
This documentation uses the SQL mirror in its examples under the db name:
importio.getquill._lazyvaldb= source(newSqlMirrorSourceConfig("testSource"))Quotations are both compile-time and runtime values. Quill uses a type refinement to store the quotation's AST as an annotation available at compile-time and the q.ast method exposes the AST as runtime value.
It is important to avoid giving explicit types to quotations when possible. For instance, this quotation can't be read at compile-time as the type refinement is lost:
// Avoid type widening (Quoted[Query[Circle]]), or else the quotation will be dynamic.valq:Quoted[Query[Circle]] = quote {
query[Circle].filter(c => c.radius >10)
}
db.run(q) // Dynamic queryQuill falls back to runtime normalization and query generation if the quotation's AST can be read at compile-time. Please refer to dynamic queries for more information
Quotations are designed to be self-contained, without references to runtime values outside their scope. There are two mechanisms to explicitly bind runtime values to a quotation execution.
A runtime value can be lifted to a quotation through the method lift:
defbiggerThan(i: Float) = quote {
query[Circle].filter(r => r.radius > lift(i))
}
db.run(biggerThan(10)) // SELECT r.radius FROM Circle r WHERE r.radius > ?A quotation can be defined as a function:
valbiggerThan= quote {
(i: Int) =>
query[Circle].filter(r => r.radius > i)
}And a runtime value can be specified when running it:
db.run(biggerThan)(10) // SELECT r.radius FROM Circle r WHERE r.radius > ?The database schema is represented by case classes. By default, quill uses the class and field names as the database identifiers:
caseclassCircle(radius: Float)
valq= quote {
query[Circle].filter(c => c.radius >1)
}
db.run(q) // SELECT c.radius FROM Circle c WHERE c.radius > 1Alternatively, the identifiers can be customized:
valcircles= quote {
query[Circle].schema(_.entity("circle_table").columns(_.radius ->"radius_column"))
}
valq= quote {
circles.filter(c => c.radius >1)
}
db.run(q)
// SELECT c.radius_column FROM circle_table c WHERE c.radius_column > 1If multiple tables require custom identifiers, it is good practice to define a schema object with all table queries to be reused across multiple queries:
caseclassCircle(radius: Int)
caseclassRectangle(length: Int, width: Int)
objectschema {
valcircles= quote {
query[Circle].schema(
_.entity("circle_table")
.columns(_.radius ->"radius_column"))
}
valrectangles= quote {
query[Rectangle].schema(
_.entity("rectangle_table")
.columns(
_.length ->"length_column",
_.width ->"width_column"))
}
}It is possible to define a column that is a key generated by the database. It will be ignored during insertions and returned as the result.
Note that it accepts only values that can be read as Long.
caseclassProduct(id: Long, description: String, sku: Long)
valq= quote {
query[Product].schema(_.generated(_.id)).insert
}
db.run(q)
// INSERT INTO Product (description,sku) VALUES (?, ?)The overall abstraction of quill queries is use database tables as if they were in-memory collections. Scala for-comprehensions provide syntatic sugar to deal with this kind of monadic operations:
caseclassPerson(id: Int, name: String, age: Int)
caseclassContact(personId: Int, phone: String)
valq= quote {
for {
p <- query[Person] if(p.id ==999)
c <- query[Contact] if(c.personId == p.id)
} yield {
(p.name, c.phone)
}
}
db.run(q)
// SELECT p.name, c.phone FROM Person p, Contact c WHERE (p.id = 999) AND (c.personId = p.id)Quill normalizes the quotation and translates the monadic joins to applicative joins, generating a database-friendly query that avoids nested queries.
Any of the following features can be used together with the others and/or within a for-comprehension:
filter
valq= quote {
query[Person].filter(p => p.age >18)
}
db.run(q)
// SELECT p.id, p.name, p.age FROM Person p WHERE p.age > 18map
valq= quote {
query[Person].map(p => p.name)
}
db.run(q)
// SELECT p.name FROM Person pflatMap
valq= quote {
query[Person].filter(p => p.age >18).flatMap(p => query[Contact].filter(c => c.personId == p.id))
}
db.run(q)
// SELECT c.personId, c.phone FROM Person p, Contact c WHERE (p.age > 18) AND (c.personId = p.id)sortBy
valq1= quote {
query[Person].sortBy(p => p.age)
}
db.run(q1)
// SELECT p.id, p.name, p.age FROM Person p ORDER BY p.age ASC NULLS FIRSTvalq2= quote {
query[Person].sortBy(p => p.age)(Ord.descNullsLast)
}
db.run(q2)
// SELECT p.id, p.name, p.age FROM Person p ORDER BY p.age DESC NULLS LASTvalq3= quote {
query[Person].sortBy(p => (p.name, p.age))(Ord(Ord.asc, Ord.desc))
}
db.run(q3)
// SELECT p.id, p.name, p.age FROM Person p ORDER BY p.name ASC, p.age DESCdrop/take
valq= quote {
query[Person].drop(2).take(1)
}
db.run(q)
// SELECT x.id, x.name, x.age FROM Person x LIMIT 1 OFFSET 2groupBy
valq= quote {
query[Person].groupBy(p => p.age).map {
case (age, people) =>
(age, people.size)
}
}
db.run(q)
// SELECT p.age, COUNT(*) FROM Person p GROUP BY p.ageunion
valq= quote {
query[Person].filter(p => p.age >18).union(query[Person].filter(p => p.age >60))
}
db.run(q)
// SELECT x.id, x.name, x.age FROM (SELECT id, name, age FROM Person p WHERE p.age > 18// UNION SELECT id, name, age FROM Person p1 WHERE p1.age > 60) xunionAll/++
valq= quote {
query[Person].filter(p => p.age >18).unionAll(query[Person].filter(p => p.age >60))
}
db.run(q)
// SELECT x.id, x.name, x.age FROM (SELECT id, name, age FROM Person p WHERE p.age > 18// UNION ALL SELECT id, name, age FROM Person p1 WHERE p1.age > 60) xvalq2= quote {
query[Person].filter(p => p.age >18) ++ query[Person].filter(p => p.age >60)
}
db.run(q2)
// SELECT x.id, x.name, x.age FROM (SELECT id, name, age FROM Person p WHERE p.age > 18// UNION ALL SELECT id, name, age FROM Person p1 WHERE p1.age > 60) xaggregation
valr= quote {
query[Person].map(p => p.age)
}
db.run(r.min) // SELECT MIN(p.age) FROM Person p
db.run(r.max) // SELECT MAX(p.age) FROM Person p
db.run(r.avg) // SELECT AVG(p.age) FROM Person p
db.run(r.sum) // SELECT SUM(p.age) FROM Person p
db.run(r.size) // SELECT COUNT(p.age) FROM Person pisEmpty/nonEmpty
valq= quote {
query[Person].filter{ p1 =>
query[Person].filter(p2 => p2.id != p1.id && p2.age == p1.age).isEmpty
}
}
db.run(q)
// SELECT p1.id, p1.name, p1.age FROM Person p1 WHERE// NOT EXISTS (SELECT * FROM Person p2 WHERE (p2.id <> p1.id) AND (p2.age = p1.age))valq2= quote {
query[Person].filter{ p1 =>
query[Person].filter(p2 => p2.id != p1.id && p2.age == p1.age).nonEmpty
}
}
db.run(q2)
// SELECT p1.id, p1.name, p1.age FROM Person p1 WHERE// EXISTS (SELECT * FROM Person p2 WHERE (p2.id <> p1.id) AND (p2.age = p1.age))contains
valq= quote {
query[Person].filter(p =>Set(1, 2).contains(p.id))
}
db.run(q)
// SELECT p.id, p.name, p.age FROM Person p WHERE p.id IN (1, 2)valq1= quote { (ids: Set[Int]) =>
query[Person].filter(p => ids.contains(p.id))
}
db.run(q1)
// SELECT p.id, p.name, p.age FROM Person p WHERE p.id IN (?)valpeopleWithContacts= quote {
query[Person].filter(p => query[Contact].filter(c => c.personId == p.id).nonEmpty)
}
valq2= quote {
query[Person].filter(p => peopleWithContacts.contains(p.id))
}
db.run(q2)
// SELECT p.id, p.name, p.age FROM Person p WHERE p.id IN (SELECT p1.* FROM Person p1 WHERE EXISTS (SELECT c.* FROM Contact c WHERE c.personId = p1.id))distinct
valq= quote {
query[Person].map(p => p.age).distinct
}
db.run(q)
// SELECT DISTINCT p.age FROM Person pjoins
In addition to applicative joins Quill also supports explicit joins (both inner and left/right/full outer joins).
valq= quote {
query[Person].join(query[Contact]).on((p, c) => c.personId == p.id)
}
db.run(q)
// SELECT p.id, p.name, p.age, c.personId, c.phone•// FROM Person p INNER JOIN Contact c ON c.personId = p.idvalq= quote {
query[Person].leftJoin(query[Contact]).on((p, c) => c.personId == p.id)
}
db.run(q)
// SELECT p.id, p.name, p.age, c.personId, c.phone•// FROM Person p LEFT JOIN Contact c ON c.personId = p.idThe example joins above cover the simple case. What do you do when a query requires joining more than 2 tables?
With Quill the following multi-join queries are equivalent, choose according to preference:
caseclassEmployer(id: Int, personId: Int, name: String)
valqFlat= quote {
for{
(p,e) <- query[Person].join(query[Employer]).on(_.id == _.personId)
c <- query[Contact].leftJoin(_.personId == p.id)
} yield(p, e, c)
}
valqNested= quote {
for{
((p,e),c) <-
query[Person].join(query[Employer]).on(_.id == _.personId)
.leftJoin(query[Contact]).on(
_._1.id == _.personId
)
} yield(p, e, c)
}
db.run(qFlat)
db.run(qNested)
// SELECT p.id, p.name, p.age, e.id, e.personId, e.name, c.id, c.phone•// FROM Person p INNER JOIN Employer e ON p.id = e.personId LEFT JOIN Contact c ON c.personId = p.idQuery probing is an experimental feature that validates queries against the database at compile time, failing the compilation if it is not valid. The query validation does not alter the database state.
This feature is disabled by default. To enable it, mix the QueryProbing trait to the database configuration:
lazy val db = source(new MySourceConfig("configKey") with QueryProbing)
The config configuration must be self-contained, not having references to variables outside its scope. This allows the macro load the source instance at compile-time.
The configurations correspondent to the config key must be available at compile time. You can achieve it by adding this line to your project settings:
unmanagedClasspath in Compile += baseDirectory.value / "src" / "main" / "resources"
If your project doesn't have a standard layout, e.g. a play project, you should configure the path to point to the folder that contains your config file.
Database actions are defined using quotations as well. These actions don't have a collection-like API but rather a custom DSL to express inserts, deletes and updates.
Note: Actions take either a List (in which case the query is batched) or a single value.
insert
vala= quote(query[Contact].insert)
db.run(a)(List(Contact(999, "+1510488988")))
// INSERT INTO Contact (personId,phone) VALUES (?, ?)
db.run(a)(Contact(999, "+1510488988"))
// insert single itemIt is also possible to insert specific columns:
vala= quote {
(personId: Int, phone: String) =>
query[Contact].insert(_.personId -> personId, _.phone -> phone)
}
db.run(a)(List((999, "+1510488988")))
// INSERT INTO Contact (personId,phone) VALUES (?, ?)Or column queries:
vala= quote {
(id: Int) =>
query[Person].insert(_.id -> id, _.age -> query[Person].map(p => p.age).max)
}
db.run(a)(List(999))
// INSERT INTO Person (id,age) VALUES (?, (SELECT MAX(p.age) FROM Person p))update
vala= quote {
query[Person].filter(_.id ==999).update
}
db.run(a)(List(Person(999, "John", 22)))
// UPDATE Person SET id = ?, name = ?, age = ? WHERE id = 999
db.run(a)(Person(999, "John", 22))
// update single itemUsing specific columns:
vala= quote {
(id: Int, age: Int) =>
query[Person].filter(p => p.id == id).update(_.age -> age)
}
db.run(a)(List((999, 18)))
// UPDATE Person SET age = ? WHERE id = ?Using columns as part of the update:
vala= quote {
(id: Int) =>
query[Person].filter(p => p.id == id).update(p => p.age -> (p.age +1))
}
db.run(a)(List(999))
// UPDATE Person SET age = (age + 1) WHERE id = ?Using column a query:
vala= quote {
(id: Int) =>
query[Person].filter(p => p.id == id).update(_.age -> query[Person].map(p => p.age).max)
}
db.run(a)(List(999))
// UPDATE Person SET age = (SELECT MAX(p.age) FROM Person p) WHERE id = ?delete
vala= quote {
query[Person].filter(p => p.name =="").delete
}
db.run(a)
// DELETE FROM Person WHERE name = ''You can use the JdbcSource object’s transaction method to create a transaction when you need one:
vala= quote(query[Contact].insert)
db.transaction { transactional =>
transactional.run(a)(Contact(555, "+4410488555"))
transactional.run(a)(Contact(999, "+1510488988"))
}The code passed to it is executed in a single transaction. If an exception is thrown, Quill rolls back the transaction at the end of the block.
Quill provides implicit conversions from case class companion objects to query[T] through an extra import:
importio.getquill.ImplicitQuery._valq= quote {
for {
p <-Personif(p.id ==999)
c <-Contactif(c.personId == p.id)
} yield {
(p.name, c.phone)
}
}
db.run(q)
// SELECT p.name, c.phone FROM Person p, Contact c WHERE (p.id = 999) AND (c.personId = p.id)Note the usage of Person and Contact instead of query[Person] and query[Contact].
Some operations are sql-specific and not provided with the generic quotation mechanism. The io.getquill.sources.sql.ops package has some implicit classes for this kind of operations:
like
importio.getquill.sources.sql.ops._valq= quote {
query[Person].filter(p => p.name like "%John%")
}
db.run(q)
// SELECT p.id, p.name, p.age FROM Person p WHERE p.name like '%John%'The cql-specific operations are provided by the following import:
importio.getquill.sources.cassandra.ops._The cassandra package also offers a mirror source:
importio.getquill._lazyvaldb= source(newCassandraMirrorSourceConfig("testSource"))Supported operations:
allowFiltering
valq= quote {
query[Person].filter(p => p.age >10).allowFiltering
}
db.run(q)
// SELECT id, name, age FROM Person WHERE age > 10 ALLOW FILTERINGifNotExists
valq= quote {
query[Person].insert(_.age ->10, _.name ->"John").ifNotExists
}
db.run(q)
// INSERT INTO Person (age,name) VALUES (10, 'John') IF NOT EXISTSifExists
valq= quote {
query[Person].filter(p => p.name =="John").delete.ifExists
}
db.run(q)
// DELETE FROM Person WHERE name = 'John' IF EXISTSusingTimestamp
valq1= quote {
query[Person].insert(_.age ->10, _.name ->"John").usingTimestamp(99)
}
db.run(q1)
// INSERT INTO Person (age,name) VALUES (10, 'John') USING TIMESTAMP 99valq2= quote {
query[Person].usingTimestamp(99).update(_.age ->10)
}
db.run(q2)
// UPDATE Person USING TIMESTAMP 99 SET age = 10usingTtl
valq1= quote {
query[Person].insert(_.age ->10, _.name ->"John").usingTtl(11)
}
db.run(q1)
// INSERT INTO Person (age,name) VALUES (10, 'John') USING TTL 11valq2= quote {
query[Person].usingTtl(11).update(_.age ->10)
}
db.run(q2)
// UPDATE Person USING TTL 11 SET age = 10valq3= quote {
query[Person].usingTtl(11).filter(_.name =="John").delete
}
db.run(q3) // DELETE FROM Person USING TTL 11 WHERE name = 'John'using
valq1= quote {
query[Person].insert(_.age ->10, _.name ->"John").using(ts =99, ttl =11)
}
db.run(q1)
// INSERT INTO Person (age,name) VALUES (10, 'John') USING TIMESTAMP 99 AND TTL 11valq2= quote {
query[Person].using(ts =99, ttl =11).update(_.age ->10)
}
db.run(q2)
// UPDATE Person USING TIMESTAMP 99 AND TTL 11 SET age = 10valq3= quote {
query[Person].using(ts =99, ttl =11).filter(_.name =="John").delete
}
db.run(q3)
// DELETE FROM Person USING TIMESTAMP 99 AND TTL 11 WHERE name = 'John'ifCond
valq1= quote {
query[Person].update(_.age ->10).ifCond(_.name =="John")
}
db.run(q1)
// UPDATE Person SET age = 10 IF name = 'John'valq2= quote {
query[Person].filter(_.name =="John").delete.ifCond(_.age ==10)
}
db.run(q2)
// DELETE FROM Person WHERE name = 'John' IF age = 10delete column
valq= quote {
query[Person].map(p => p.age).delete
}
db.run(q)
// DELETE p.age FROM PersonQuill's default operation mode is compile-time, but there are queries that have their structure defined only at runtime. Quill automatically falls back to runtime normalization and query generation if the query's structure is not static. Example:
importio.getquill._lazyvaldb= source(newSqlMirrorSourceConfig("testSource"))
sealedtraitQueryTypecaseobjectMinorextendsQueryTypecaseobjectSeniorextendsQueryTypedefpeople(t: QueryType):Quoted[Query[Person]] =
t match {
caseMinor=> quote {
query[Person].filter(p => p.age <18)
}
caseSenior=> quote {
query[Person].filter(p => p.age >65)
}
}
db.run(people(Minor))
// SELECT p.id, p.name, p.age FROM Person p WHERE p.age < 18
db.run(people(Senior))
// SELECT p.id, p.name, p.age FROM Person p WHERE p.age > 65Infix is a very flexible mechanism to use non-supported features without having to use plain queries in the target language. It allows insertion of arbitrary strings within quotations.
For instance, quill doesn't support the FOR UPDATE SQL feature. It can still be used through infix and implicit classes:
implicitclassForUpdate[T](q: Query[T]) {
defforUpdate= quote(infix"$q FOR UPDATE".as[Query[T]])
}
vala= quote {
query[Person].filter(p => p.age <18).forUpdate
}
db.run(a)
// SELECT p.id, p.name, p.age FROM (SELECT * FROM Person p WHERE p.age < 18 FOR UPDATE) pThe forUpdate quotation can be reused for multiple queries.
The same approach can be used for RETURNING ID:
implicitclassReturningId[T](a: Action[T]) {
defreturningId= quote(infix"$a RETURNING ID".as[Action[T]])
}
vala= quote {
query[Person].insert(_.name ->"John", _.age ->21).returningId
}
db.run(a)
// INSERT INTO Person (name,age) VALUES ('John', 21) RETURNING IDA custom database function can also be used through infix:
valmyFunction= quote {
(i: Int) =>infix"MY_FUNCTION($i)".as[Int]
}
valq= quote {
query[Person].map(p => myFunction(p.age))
}
db.run(q)
// SELECT MY_FUNCTION(p.age) FROM Person pQuill uses Encoders to encode query inputs and Decoders to read values returned by queries. The library provides a few built-in encodings and two mechanisms to define custom encodings: mapped encoding and raw encoding.
If the correspondent database type is already supported, use mappedEncoding. In this example, String is already supported by Quill and the UUID encoding from/to String is defined through mapped encoding:
importjava.util.UUIDimplicitvalencodeUUID= mappedEncoding[UUID, String](_.toString)
implicitvaldecodeUUID= mappedEncoding[String, UUID](UUID.fromString(_))If the database type is not supported by Quill, it is possible to provide "raw" encoders and decoders:
importio.getquill.naming.Literalimportio.getquill.sources.jdbc.JdbcSourceimportio.getquill.sources.sql.idiom.PostgresDialecttraitUUIDEncodingExample {
valjdbcSource:JdbcSource[PostgresDialect, Literal] // your source should go hereimportjdbcSource._implicitvaluuidDecoder:Decoder[UUID] =
decoder[UUID] {
row => index =>UUID.fromString(row.getObject(index).toString) // database-specific implementation
}
implicitvaluuidEncoder:Encoder[UUID] =
encoder[UUID] {
row => (idx, uuid) =>
row.setObject(idx, uuid, java.sql.Types.OTHER) // database-specific implementation
}
}Quill also supports encoding of "wrapped types". Just extend the WrappedValue trait and Quill will automatically encode the underlying primitive type.
importio.getquill.sources._caseclassUserId(value: Int) extendsAnyValwithWrappedValue[Int]
caseclassUser(id: UserId, name: String)
valq= quote {
(id: UserId) =>for {
u <- query[User] if u.id == id
} yield u
}
db.run(q)(UserId(1))
// SELECT u.id, u.name FROM User u WHERE (u.id = 1)Sources represent the database and provide an execution interface for queries. Example:
importio.getquill._importio.getquill.naming.SnakeCaseimportio.getquill.sources.sql.idiom.MySQLDialectlazyvaldb= source(newJdbcSourceConfig[MySQLDialect, SnakeCase]("db"))The SQL dialect to be used by the source is defined by the first type parameter. Some source types are specific to a database and thus not require it.
Quill has three built-in dialects:
io.getquill.sources.sql.idiom.H2Dialectio.getquill.sources.sql.idiom.MySQLDialectio.getquill.sources.sql.idiom.PostgresDialect
The second type parameter defines the naming strategy to be used when translating identifiers (table and column names) to SQL.
| strategy | example |
|---|---|
io.getquill.naming.Literal | some_ident -> some_ident |
io.getquill.naming.Escape | some_ident -> "some_ident" |
io.getquill.naming.UpperCase | some_ident -> SOME_IDENT |
io.getquill.naming.LowerCase | SOME_IDENT -> some_ident |
io.getquill.naming.SnakeCase | someIdent -> some_ident |
io.getquill.naming.CamelCase | some_ident -> someIdent |
io.getquill.naming.MysqlEscape | some_ident -> `some_ident` |
io.getquill.naming.PostgresEscape | $some_ident -> $some_ident |
Multiple transformations can be defined using mixin. For instance, the naming strategy
SnakeCase with UpperCase
produces the following transformation:
someIdent -> SOME_IDENT
The transformations are applied from left to right.
The string passed to the source configuration is used as the key to obtain configurations using the typesafe config library.
Additionally, any member of a source configuration can be overriden. Example:
import io.getquill._
import io.getquill.naming.SnakeCase
import io.getquill.sources.sql.idiom.MySQLDialect
lazy val db = source(new JdbcSourceConfig[MySQLDialect, SnakeCase]("db") {
override def dataSource = ??? // create the datasource manually
})
Quill uses HikariCP for connection pooling. Please refer to HikariCP's documentation for a detailed explanation of the available configurations.
Note that there are dataSource configurations, that go under dataSource, like user and password, but some pool settings may go under the root config, like connectionTimeout.
MySQL
sbt dependencies
libraryDependencies ++= Seq(
"mysql" % "mysql-connector-java" % "5.1.36",
"io.getquill" %% "quill-jdbc" % "0.6.1-SNAPSHOT"
)
source definition
importio.getquill._importio.getquill.naming.SnakeCaseimportio.getquill.sources.sql.idiom.MySQLDialectlazyvaldb= source(newJdbcSourceConfig[MySQLDialect, SnakeCase]("db"))application.properties
db.dataSourceClassName=com.mysql.jdbc.jdbc2.optional.MysqlDataSource
db.dataSource.url=jdbc:mysql://host/database
db.dataSource.user=root
db.dataSource.password=root
db.dataSource.cachePrepStmts=true
db.dataSource.prepStmtCacheSize=250
db.dataSource.prepStmtCacheSqlLimit=2048
db.connectionTimeout=30000
Postgres
sbt dependencies
libraryDependencies ++= Seq(
"org.postgresql" % "postgresql" % "9.4-1206-jdbc41",
"io.getquill" %% "quill-jdbc" % "0.6.1-SNAPSHOT"
)
source definition
importio.getquill._importio.getquill.naming.SnakeCaseimportio.getquill.sources.sql.idiom.PostgresDialectlazyvaldb= source(newJdbcSourceConfig[PostgresDialect, SnakeCase]("db"))application.properties
db.dataSourceClassName=org.postgresql.ds.PGSimpleDataSource
db.dataSource.user=root
db.dataSource.password=root
db.dataSource.databaseName=database
db.dataSource.portNumber=5432
db.dataSource.serverName=host
db.connectionTimeout=30000
MySQL Async
sbt dependencies
libraryDependencies ++= Seq(
"io.getquill" %% "quill-async" % "0.6.1-SNAPSHOT"
)
source definition
importio.getquill._importio.getquill.naming.SnakeCaselazyvaldb= source(newMysqlAsyncSourceConfig[SnakeCase]("db"))application.properties
db.host=host
db.port=3306
db.user=root
db.password=root
db.database=database
db.poolMaxQueueSize=4
db.poolMaxObjects=4
db.poolMaxIdle=999999999
db.poolValidationInterval=100
Postgres Async
sbt dependencies
libraryDependencies ++= Seq(
"io.getquill" %% "quill-async" % "0.6.1-SNAPSHOT"
)
source definition
importio.getquill._importio.getquill.naming.SnakeCaselazyvaldb= source(newPostgresAsyncSourceConfig[SnakeCase]("db"))application.properties
db.host=host
db.port=5432
db.user=root
db.password=root
db.database=database
db.poolMaxQueueSize=4
db.poolMaxObjects=4
db.poolMaxIdle=999999999
db.poolValidationInterval=100
sbt dependencies
libraryDependencies ++= Seq(
"io.getquill" %% "quill-finagle-mysql" % "0.6.1-SNAPSHOT"
)
source definition
importio.getquill._importio.getquill.naming.SnakeCaselazyvaldb= source(newFinagleMysqlSourceConfig[SnakeCase]("db"))application.properties
db.dest=localhost:3306
db.user=root
db.password=root
db.database=database
db.pool.watermark.low=0
db.pool.watermark.high=10
db.pool.idleTime=5 # seconds
db.pool.bufferSize=0
db.pool.maxWaiters=2147483647
sbt dependencies
libraryDependencies ++= Seq(
"io.getquill" %% "quill-cassandra" % "0.6.1-SNAPSHOT"
)
synchronous source
importio.getquill._importio.getquill.naming.SnakeCaselazyvaldb= source(newCassandraSyncSourceConfig[SnakeCase]("db"))asynchronous source
importio.getquill._importio.getquill.naming.SnakeCaselazyvaldb= source(newCassandraAsyncSourceConfig[SnakeCase]("db"))stream source
importio.getquill._importio.getquill.naming.SnakeCaselazyvaldb= source(newCassandraStreamSourceConfig[SnakeCase]("db"))The configurations are set using runtime reflection on the Cluster.builder instance. It is possible to set nested structures like queryOptions.consistencyLevel, use enum values like LOCAL_QUORUM, and set multiple parameters like in credentials.
application.properties
db.keyspace=quill_test
db.preparedStatementCacheSize=1000
db.session.contactPoint=127.0.0.1
db.session.queryOptions.consistencyLevel=LOCAL_QUORUM
db.session.withoutMetrics=true
db.session.withoutJMXReporting=false
db.session.credentials.0=root
db.session.credentials.1=pass
db.session.maxSchemaAgreementWaitSeconds=1
db.session.addressTranslater=com.datastax.driver.core.policies.IdentityTranslater
In order to quickly start with Quill, we have setup some template projects:
Please refer to SLICK.md for a detailed comparison between Quill and Slick.
Please refer to CASSANDRA.md for a detailed comparison between Quill and other main alternatives for interaction with Cassandra in Scala.
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. See CODE_OF_CONDUCT.md for details.
See the LICENSE file for details.
- @fwbrasil
- @godenji
- @gustavoamigo
- @jilen
- @lvicentesanchez
You can notify all maintainers using the handle @getquill/maintainers.
The project was created having Philip Wadler's talk "A practical theory of language-integrated query" as its initial inspiration. The development was heavily influenced by the following papers:

