ZIO SQL lets you write type-safe, type-inferred, and composable SQL queries in ordinary Scala, helping you prevent persistence bugs before they happen, and leverage your IDE to make writing SQL productive, safe, and fun.
ZIO SQL can be used as a library for modeling SQL in a type-safe ADT. In addition, ZIO SQL has a JDBC interface, which utilizes the type-safe SQL ADT for interacting with common JDBC databases.
For the JDBC module:
ZIO SQL does not offer Language Integrated Queries (LINQ) or similar functionality. It is intended only as a data model for representing SQL queries and an accompanying lightweight JDBC-based executor.
:heavy_check_mark: - good to go
:white_check_mark: - some more work needed
Feature | Progress |
---|---|
Type-safe schema | :heavy_check_mark: |
Type-safe DSL | :heavy_check_mark: |
Running Reads | :heavy_check_mark: |
Running Deletes | :heavy_check_mark: |
Running Updates | :heavy_check_mark: |
Running Inserts | :heavy_check_mark: |
Transactions | :white_check_mark: |
Connection pool | :white_check_mark: |
Feature | PostgreSQL | SQL Server | Oracle | MySQL |
---|---|---|---|---|
Render Read | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
Render Delete | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
Render Update | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
Render Insert | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
Functions | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
Types | :white_check_mark: | :white_check_mark: | ||
Operators |
ZIO SQL is packaged into separate modules for different databases. Depending on which of these (currently supported) systems you're using, you will need to add one of the following dependencies:
//PostgreSQL
libraryDependencies += "dev.zio" %% "zio-sql-postgres" % "0.1.2"
//MySQL
libraryDependencies += "dev.zio" %% "zio-sql-mysql" % "0.1.2"
//Oracle
libraryDependencies += "dev.zio" %% "zio-sql-oracle" % "0.1.2"
//SQL Server
libraryDependencies += "dev.zio" %% "zio-sql-sqlserver" % "0.1.2"
Most of the needed imports will be resolved with
import zio.sql._
ZIO SQL relies heavily on path dependent types, so to use most of the features you need to be in the scope of one of the database modules:
trait MyRepositoryModule extends PostgresModule {
// your ZIO SQL code here
}
// other available modules are MysqlModule, OracleModule and SqlServerModule
We will assume this scope in the following examples.
In order to construct correct and type-safe queries, we need to describe tables by writing user defined data type - case class in which name of the case class represents table name, field names represent column names and field types represent column types.
Values that will represent tables in DSL are then created by calling defineTable
method which takes case class type parameter.
In order for defineTable
to work, user need to provide implicit Schema
of data type.
import zio.schema.DeriveSchema
import zio.sql.postgresql.PostgresJdbcModule
import zio.sql.table.Table._
import java.time._
import java.util.UUID
object Repository extends PostgresJdbcModule {
final case class Product(id: UUID, name: String, price: BigDecimal)
implicit val productSchema = DeriveSchema.gen[Product]
val products = defineTableSmart[Product]
final case class Order(id: UUID, productId: UUID, quantity: Int, orderDate: LocalDate)
implicit val orderSchema = DeriveSchema.gen[Order]
val orders = defineTable[Order]
}
defineTable
method is overloaded with an alternative that takes table name as an input. User can also specify table name using @name
annotation.
Alternatively user can use defineTableSmart
method which will smartly pluralize table name according to english grammar.
OrderOrigin
-> order_origins
Foot
-> feet
PersonAddress
-> person_addresses
Field names are also converted to lowercase and snake case.
productId
-> product_id
and so on.
Once we have our table definition we need to decompose table into columns which we will use in queries.
Using the previous example with Product
and Order
table
val (id, name, price) = products.columns
val (orderId, productId, quantity, date) = orders.columns
Simple select.
val allProducts = select(id, name, price).from(products)
Using where
clause.
def productById(uuid: UUID) =
select(id, name, price).from(products).where(id === uuid)
Inner join.
val ordersWithProductNames =
select(orderId, name).from(products.join(orders).on(productId === id))
Left outer join.
val leftOuter =
select(orderId, name).from(products.leftOuter(orders).on(productId === id))
Right outer join.
val rightOuter =
select(orderId, name).from(products.rightOuter(orders).on(productId === id))
Using limit
and offset
val limitedResults =
select(orderId, name)
.from(products.join(orders)
.on(productId === id))
.limit(5)
.offset(10)
def insertProduct(uuid: UUID) =
insertInto(products)(id, name, price)
.values((uuid, "Zionomicon", 10.5))
def updateProduct(uuid: UUID) =
update(products)
.set(name, "foo")
.set(price, price * 1.1)
.where(id === uuid)
def deleteProduct(uuid: UUID) =
deleteFrom(products)
.where(id === uuid)
TODO: details
TODO: details
TODO: details
Learn more on the ZIO SQL homepage!
For the general guidelines, see ZIO contributor's guide.### TL;DR Prerequisites (installed):
Technology | Version |
---|---|
sbt | 1.4.3 |
Docker | 3.1 |
To set up the project follow below steps:
sbt
and Docker
.sbt test
.See the Code of Conduct