SQL ORM

Using the SQL ORM

This template uses ZIO Quill (opens in a new tab), an ORM that uses a Quoted Domain Specific Language (QDSL) to generate SQL queries from your functional code.

The necessary steps are already done if you have set add_sql_orm to true, but we still will go over them, so you can understand.

Setup

Create db

You need to start a postgres database with the correct schema (the one you want) if you want to use our example you need to create a example_entity table with the following schema:

CREATE TABLE example_entity (
    id VARCHAR(255) NOT NULL,
    value INT NOT NULL,
    PRIMARY KEY (id)
);

Environment variables

You will also need to configure the connection to your database, you can do this by setting the following environment variables:

export POSTGRES_USER=postgres;
export POSTGRES_PASSWORD=postgres;
export POSTGRES_HOST=localhost;
export POSTGRES_PORT=5432;
export POSTGRES_NAME=postgres;

Note : The default values are the ones used in the example above. You can run the server without setting any environment variables.

Code

First define your model:

final case class ExampleEntity(id: String, value: Int)

You can now inject quill into a service that will need it ...

//                    👇 Inject quill with the Postgres dialect and the snake case naming strategy
class ExampleService(quill: Quill.Postgres[SnakeCase]) {
  import quill._ // 👈 Import the quill context, it allows your controller to execute the run function
 
  def getById(id: String): ZIO[Any, SQLException, Option[ExampleEntity]] = {
    run(quote {
      query[ExampleEntity].filter(_.id == lift(id))
    }).map(_.headOption)
  }
 //...
}

We now add a ZLayer to our ExampleService so the provided quill context will be available to our controller:

object ExampleService {
  val live = ZLayer.fromFunction(new ExampleService(_))
}

You can finally provide the configuration to our service, here we do it in main:

object Main extends ZIOAppDefault {
 
  override def run =
    for {
 
      _ <- ZIO.serviceWithZIO[ExampleService](
        _.insertOne(randomUUID.toString, 1)
      ).provide(
        ExampleService.live, // 👈 Provide the service so it can be constructed with the quill context
        Quill.Postgres.fromNamingStrategy(SnakeCase),
        Quill.DataSource.fromPrefix("db.default") // 👈 Provide the configuration
      ).debug(
        "Result: "
      )
 
    } yield 0
}

And now you are good to go! 🚀