Operations (SELECT, INSERT, UPDATE, DELETE)

After defining some queries, the next step is to run them. Running queries requires an instance of Db[F, Codec]. These docs will use DataSourceDb.

import dataprism.KMacros
import dataprism.sql.{Table, Column}
import dataprism.jdbc.sql.{JdbcCodec, DataSourceDb}
import dataprism.jdbc.sql.PostgresJdbcTypes.*
import scala.concurrent.Future

case class UserK[F[_]](
  id: F[Int],
  name: F[Option[String]],
  username: F[String],
  email: F[String]
)

object UserK:
  // Snippet compiler fails here sadly
  given KMacros.ApplyTraverseKC[UserK] = ??? // KMacros.deriveApplyTraverseKC[UserK]

  val table: Table[JdbcCodec, UserK] = Table(
    "users",
    UserK(
      Column("id", integer),
      Column("name", text.nullable),
      Column("username", text),
      Column("email", text)
    )
  )

import scala.concurrent.ExecutionContext.Implicits.global

given DataSourceDb[Future] = DataSourceDb.ofFuture(???)
Setup.scala

Select

The simplest operation is Select, used like so.

import dataprism.KMacros
import dataprism.sql.{Table, Column}
import dataprism.jdbc.sql.{JdbcCodec, DataSourceDb}
import dataprism.jdbc.sql.PostgresJdbcTypes.*
import scala.concurrent.Future

case class UserK[F[_]](
  id: F[Int],
  name: F[Option[String]],
  username: F[String],
  email: F[String]
)

object UserK:
  // Snippet compiler fails here sadly
  given KMacros.ApplyTraverseKC[UserK] = ??? // KMacros.deriveApplyTraverseKC[UserK]

  val table: Table[JdbcCodec, UserK] = Table(
    "users",
    UserK(
      Column("id", integer),
      Column("name", text.nullable),
      Column("username", text),
      Column("email", text)
    )
  )

import scala.concurrent.ExecutionContext.Implicits.global

given DataSourceDb[Future] = DataSourceDb.ofFuture(???)

import dataprism.jdbc.platform.PostgresJdbcPlatform.Api.{*, given}
import scala.concurrent.Future
import perspective.Id

val data: Future[Seq[UserK[Id]]] = Select(Query.from(UserK.table)).run

Insert

Next up is insert. There are a few ways to do this depending on what you want to insert. Do you insert values from outside or inside the database. Do you want to insert into all the columns of the table, or leave some for the database to handle?

The simples way is just Insert.into(table).values(value, values*). For this function you pass in the table to insert, and values to insert. The next option is Insert.into(table).valuesFromQuery(query).

import dataprism.KMacros
import dataprism.sql.{Table, Column}
import dataprism.jdbc.sql.{JdbcCodec, DataSourceDb}
import dataprism.jdbc.sql.PostgresJdbcTypes.*
import scala.concurrent.Future

case class UserK[F[_]](
  id: F[Int],
  name: F[Option[String]],
  username: F[String],
  email: F[String]
)

object UserK:
  // Snippet compiler fails here sadly
  given KMacros.ApplyTraverseKC[UserK] = ??? // KMacros.deriveApplyTraverseKC[UserK]

  val table: Table[JdbcCodec, UserK] = Table(
    "users",
    UserK(
      Column("id", integer),
      Column("name", text.nullable),
      Column("username", text),
      Column("email", text)
    )
  )

import scala.concurrent.ExecutionContext.Implicits.global

given DataSourceDb[Future] = DataSourceDb.ofFuture(???)

import dataprism.jdbc.platform.PostgresJdbcPlatform.Api.{*, given}

Insert.into(UserK.table).values(UserK(5, Some("foo"), "bar", "foo@example.com")).run

Insert.into(UserK.table).valuesFromQuery(
  Query.from(UserK.table).filter(_.username === "foo".as(text))
).run

Sometimes you only want to insert values into some columns. To do this, use valuesInColumns(projection)(value, values*) or valuesInColumnsFromQuery(projection)(query). These functions take a projection function (simplified as) A[Column] => B[Column]. A is the type of the table while B is a projection to the columns you want to set. From there you pass in either a Query[B] or values of type B[Id]..

import dataprism.KMacros
import dataprism.sql.{Table, Column}
import dataprism.jdbc.sql.{JdbcCodec, DataSourceDb}
import dataprism.jdbc.sql.PostgresJdbcTypes.*
import scala.concurrent.Future

case class UserK[F[_]](
  id: F[Int],
  name: F[Option[String]],
  username: F[String],
  email: F[String]
)

object UserK:
  // Snippet compiler fails here sadly
  given KMacros.ApplyTraverseKC[UserK] = ??? // KMacros.deriveApplyTraverseKC[UserK]

  val table: Table[JdbcCodec, UserK] = Table(
    "users",
    UserK(
      Column("id", integer),
      Column("name", text.nullable),
      Column("username", text),
      Column("email", text)
    )
  )

import scala.concurrent.ExecutionContext.Implicits.global

given DataSourceDb[Future] = DataSourceDb.ofFuture(???)

import dataprism.jdbc.platform.PostgresJdbcPlatform.Api.{*, given}
import perspective.Compose2

Insert
  .into(UserK.table)
  .valuesInColumns(u => (u.name, u.username, u.email))((None, "bar", "bar@example.com"))
  .run

Insert
  .into(UserK.table)
  .valuesInColumnsFromQuery(u => (u.name, u.username, u.email))(
    Query.from(UserK.table)
      .filter(_.username === "foo".as(text))
      .map(user => (user.name, user.username, user.email))
  )
  .run

Update

Updates are yet a bit more complex than Inserts, and offer more choices, mostly because you can optionally add a FROM clause to the generated SQL in addition to only setting some columns, as was done with inserts. There are in general four forms

  • Update.table(table).where(cond).values(update)
  • Update.table(table).where(cond).valuesInColumns(projection)(partialUpdate)
  • Update.table(table).from(table2).where(cond).values(partialUpdate)
  • Update.table(table).from(table2).where(cond).valuesInColumns(projection)(partialUpdate)

Here are some examples:

import dataprism.KMacros
import dataprism.sql.{Table, Column}
import dataprism.jdbc.sql.{JdbcCodec, DataSourceDb}
import dataprism.jdbc.sql.PostgresJdbcTypes.*
import scala.concurrent.Future

case class UserK[F[_]](
  id: F[Int],
  name: F[Option[String]],
  username: F[String],
  email: F[String]
)

object UserK:
  // Snippet compiler fails here sadly
  given KMacros.ApplyTraverseKC[UserK] = ??? // KMacros.deriveApplyTraverseKC[UserK]

  val table: Table[JdbcCodec, UserK] = Table(
    "users",
    UserK(
      Column("id", integer),
      Column("name", text.nullable),
      Column("username", text),
      Column("email", text)
    )
  )

import scala.concurrent.ExecutionContext.Implicits.global

given DataSourceDb[Future] = DataSourceDb.ofFuture(???)

import dataprism.jdbc.platform.PostgresJdbcPlatform.Api.{*, given}
import perspective.Compose2

Update
  .table(UserK.table)
  .where(_.username === "foo".as(text))
  .values(u =>u.copy(name = u.username.asSome))
  .run

Update
  .table(UserK.table)
  .where(_.username === "foo".as(text))
  .valuesInColumns(user => user.name)(user => user.username.asSome)
  .run

Update
  .table(UserK.table)
  .from(Query.from(UserK.table))
  .where((a, b) => a.username === "foo".as(text) && b.username === "bar".as(text))
  .values((a, b) => a.copy(name = b.name))
  .run

Update
  .table(UserK.table)
  .from(Query.from(UserK.table))
  .where((a, b) => a.username === "foo".as(text) && b.username === "bar".as(text))
  .valuesInColumns(u => u.email)((a, b) =>b.email)
  .run

Delete

Lastly deletes. They are luckily a bit simpler. You can still add a USING clause, but there is nothing about only affecting some columns. Here are some examples of how Delete works.

import dataprism.KMacros
import dataprism.sql.{Table, Column}
import dataprism.jdbc.sql.{JdbcCodec, DataSourceDb}
import dataprism.jdbc.sql.PostgresJdbcTypes.*
import scala.concurrent.Future

case class UserK[F[_]](
  id: F[Int],
  name: F[Option[String]],
  username: F[String],
  email: F[String]
)

object UserK:
  // Snippet compiler fails here sadly
  given KMacros.ApplyTraverseKC[UserK] = ??? // KMacros.deriveApplyTraverseKC[UserK]

  val table: Table[JdbcCodec, UserK] = Table(
    "users",
    UserK(
      Column("id", integer),
      Column("name", text.nullable),
      Column("username", text),
      Column("email", text)
    )
  )

import scala.concurrent.ExecutionContext.Implicits.global

given DataSourceDb[Future] = DataSourceDb.ofFuture(???)

import dataprism.jdbc.platform.PostgresJdbcPlatform.Api.{*, given}

Delete.from(UserK.table).where(_.username === "foo".as(text)).run
Delete.from(UserK.table).using(Query.from(UserK.table)).where { (a, b) =>
  a.username === b.username
}.run