Skip to content

Commit

Permalink
Merge pull request #1639 from sgeorgakis/master
Browse files Browse the repository at this point in the history
[SCALA-308]- Postgres specific features in Slick using Slick-PG
  • Loading branch information
lor6 authored Nov 22, 2024
2 parents 558c080 + 7c5c026 commit 9839b0d
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 16 deletions.
21 changes: 16 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@ val shapelessVersion = "2.3.12"
val scalazVersion = "7.3.8"
val fs2Version = "3.11.0"
val reactiveMongo = "1.1.0-RC14"
val slickPgVersion = "0.22.2"
val scalaTestContainersVersion = "0.41.4"
val postgresqlVersion = "42.7.4"
val json4sVersion = "4.0.7"

lazy val scala2_libraries =
(project in file("scala-2-modules/scala2-libraries"))
Expand Down Expand Up @@ -432,7 +436,7 @@ lazy val scala_libraries = (project in file("scala-libraries"))
),
libraryDependencies ++= Seq(
"org.playframework" %% "play-slick" % LibraryVersions.playSlickVersion,
"org.postgresql" % "postgresql" % "42.7.4"
"org.postgresql" % "postgresql" % postgresqlVersion
),
dependencyOverrides := Seq(
"com.typesafe.akka" %% "akka-protobuf-v3" % AkkaVersion,
Expand Down Expand Up @@ -574,8 +578,8 @@ lazy val scala_libraries_testing = (project in file("scala-libraries-testing"))
"com.lihaoyi" %% "utest" % "0.8.4" % "test",
munitDep,
"com.amazonaws" % "aws-java-sdk-s3" % "1.12.777" % IntegrationTest,
"com.dimafeng" %% "testcontainers-scala-scalatest" % "0.41.3" % IntegrationTest,
"com.dimafeng" %% "testcontainers-scala-localstack-v2" % "0.41.3" % IntegrationTest,
"com.dimafeng" %% "testcontainers-scala-scalatest" % scalaTestContainersVersion % IntegrationTest,
"com.dimafeng" %% "testcontainers-scala-localstack-v2" % scalaTestContainersVersion % IntegrationTest,
"software.amazon.awssdk" % "s3" % "2.29.9"
),
Defaults.itSettings,
Expand All @@ -598,9 +602,16 @@ lazy val scala_libraries_persistence =
doobieCore,
doobiePGDep,
"org.reactivemongo" %% "reactivemongo" % reactiveMongo,
"org.reactivemongo" %% "reactivemongo-akkastream" % reactiveMongo,
"org.reactivemongo" %% "reactivemongo-akkastream" % reactiveMongo exclude("org.scala-lang.modules", "scala-parser-combinators_2.13"),
"de.flapdoodle.embed" % "de.flapdoodle.embed.mongo" % embedMongoVersion % IntegrationTest,
logback
logback,
"com.typesafe.slick" %% "slick-hikaricp" % slickVersion,
"org.postgresql" % "postgresql" % postgresqlVersion,
"com.github.tminglei" %% "slick-pg" % slickPgVersion,
"org.json4s" %% "json4s-native" % json4sVersion,
"com.github.tminglei" %% "slick-pg_json4s" % slickPgVersion,
"com.dimafeng" %% "testcontainers-scala-scalatest" % scalaTestContainersVersion % IntegrationTest,
"com.dimafeng" %% "testcontainers-scala-postgresql" % scalaTestContainersVersion % IntegrationTest
)
)

Expand Down
3 changes: 2 additions & 1 deletion scala-libraries-persistence/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
- [Introduction to Slick](https://www.baeldung.com/scala/slick-intro)
- [Introduction to Skunk – Scala Driver for PostgreSQL](https://www.baeldung.com/scala/skunk-postgresql-driver)
- [Introduction to doobie – a JDBC Layer for Scala](https://www.baeldung.com/scala/doobie-intro)
- [Introduction to Reactive Mongo](https://www.baeldung.com/scala/mongo-reactive-intro)
- [Introduction to Reactive Mongo](https://www.baeldung.com/scala/mongo-reactive-intro)
- [Postgres Specific Features in Slick Using Slick-PG](TBD)
14 changes: 14 additions & 0 deletions scala-libraries-persistence/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
db:
image: postgres:16.4
environment:
- POSTGRES_DB=baeldung
- POSTGRES_USER=baeldung
- POSTGRES_PASSWORD=password
ports:
- '5432:5432'
volumes:
- baeldung-postgresql-data:/var/lib/postgresql/data

volumes:
baeldung-postgresql-data:
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.baeldung.scala.slick_pg

import com.baeldung.scala.slick_pg.BaeldungPostgresProfile.api.*
import com.baeldung.scala.slick_pg.entity.BaeldungEntity
import com.baeldung.scala.slick_pg.table.baeldungEntityTable
import com.dimafeng.testcontainers.PostgreSQLContainer
import com.dimafeng.testcontainers.scalatest.TestContainerForAll
import org.json4s.native.JsonMethods
import org.scalatest.flatspec.AsyncFlatSpec
import org.testcontainers.utility.DockerImageName
import slick.dbio.DBIO

import java.time.OffsetDateTime
import scala.concurrent.Future

class BaeldungPostgresProfileSpec
extends AsyncFlatSpec
with TestContainerForAll {

override val containerDef: PostgreSQLContainer.Def = PostgreSQLContainer.Def(
dockerImageName = DockerImageName.parse("postgres:16.4"),
databaseName = "baeldung",
username = "baeldung",
password = "baeldung"
)

"BaeldungPostgresProfile" should "insert entity" in {
withContainers { container =>

val db: BaeldungPostgresProfile.backend.Database =
BaeldungPostgresProfile.backend.Database.forURL(
containerDef.start().jdbcUrl,
user = "baeldung",
password = "baeldung",
driver = "org.postgresql.Driver"
)

val createdAt = OffsetDateTime.now()
val res = db
.run(
DBIO.seq(
baeldungEntityTable.schema.createIfNotExists,
baeldungEntityTable += BaeldungEntity(
1L,
createdAt,
List(1d, 5d, 2.24d),
JsonMethods.parse("""{"field1": 5, "field2": "test"}""")
)
)
)
.flatMap(_ => db.run(baeldungEntityTable.result))

for (sequence <- res) yield {
sequence.foreach(entity => {
assert(entity.id.equals(1L))
assert(entity.createdAt.equals(createdAt))
assert(entity.prices.equals(List(1d, 5d, 2.24d)))
assert(
entity.metadata.equals(
JsonMethods.parse("""{"field1": 5, "field2": "test"}""")
)
)
})
succeed
}
}
}
}
21 changes: 11 additions & 10 deletions scala-libraries-persistence/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ h2mem {
connectionPool = disabled
}

#postgres {
# dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
# properties = {
# serverName = "localhost"
# portNumber = "5432"
# databaseName = "slick-tutorial"
# user = "postgres"
# password = "admin"
# }
#}
postgres {
connectionPool = "HikariCP"
dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
properties = {
serverName = "localhost"
portNumber = "5432"
databaseName = "baeldung"
user = "baeldung"
password = "password"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.baeldung.scala.slick_pg

import com.github.tminglei.slickpg.*
import org.json4s.{JValue, JsonMethods}
import slick.jdbc.JdbcType

trait BaeldungPostgresProfile
extends ExPostgresProfile
with PgDate2Support
with TimestamptzPostgresProfile
with PgArraySupport
with PgJson4sSupport {

override protected def computeCapabilities: Set[slick.basic.Capability] =
super.computeCapabilities + slick.jdbc.JdbcCapabilities.insertOrUpdate

override val api = BaeldungApi

override val pgjson = "jsonb"
type DOCType = org.json4s.native.Document
override val jsonMethods =
org.json4s.native.JsonMethods.asInstanceOf[JsonMethods[DOCType]]

object BaeldungApi
extends ExtPostgresAPI
with Date2DateTimeImplicitsDuration
with ArrayImplicits
with JsonImplicits {

implicit val doubleListTypeMapper: JdbcType[List[Double]] =
new SimpleArrayJdbcType[Double]("DOUBLE PRECISION").to(_.toList)
}
}

object BaeldungPostgresProfile extends BaeldungPostgresProfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.baeldung.scala.slick_pg

import com.baeldung.scala.slick_pg
import com.baeldung.scala.slick_pg.BaeldungPostgresProfile.api.*

object PostgresConnection {

val db: slick_pg.BaeldungPostgresProfile.backend.Database =
Database.forConfig("postgres")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.baeldung.scala.slick_pg

import slick.jdbc.PostgresProfile

import java.sql.{PreparedStatement, ResultSet}
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatterBuilder
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoField

trait TimestamptzPostgresProfile extends PostgresProfile {

override val columnTypes: PostgresJdbcTypes = new PostgresJdbcTypes {

override val offsetDateTimeType: OffsetDateTimeJdbcType =
new OffsetDateTimeJdbcType {

override def sqlType: Int = {
java.sql.Types.TIMESTAMP_WITH_TIMEZONE
}

private val formatter = {
new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
.optionalStart()
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 6, true)
.optionalEnd()
.optionalStart()
.appendOffset("+HH:mm:ss", "+00")
.optionalEnd()
.toFormatter()
}

override def setValue(
v: OffsetDateTime,
p: PreparedStatement,
idx: Int
): Unit = {
p.setObject(idx, v)
}
override def getValue(r: ResultSet, idx: Int): OffsetDateTime = {
r.getString(idx) match {
case null => null
case date: String => OffsetDateTime.from(formatter.parse(date))
}
}
override def updateValue(
v: OffsetDateTime,
r: ResultSet,
idx: Int
): Unit = {
r.updateObject(idx, v)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.baeldung.scala.slick_pg.entity

import org.json4s.JValue

import java.time.OffsetDateTime

case class BaeldungEntity(
id: Long,
createdAt: OffsetDateTime,
prices: List[Double],
metadata: JValue
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.baeldung.scala.slick_pg.table

import com.baeldung.scala.slick_pg.BaeldungPostgresProfile.BaeldungApi.*
import com.baeldung.scala.slick_pg.entity.BaeldungEntity
import org.json4s.JValue

import java.time.OffsetDateTime

class BaeldungEntityTable(tag: Tag)
extends Table[BaeldungEntity](tag, None, "baeldung_entity") {

val id = column[Long]("id", O.PrimaryKey, O.AutoInc, O.SqlType("BIGSERIAL"))
val createdAt = column[OffsetDateTime]("created_at", O.SqlType("TIMESTAMPTZ"))
val prices = column[List[Double]]("prices", O.SqlType("DOUBLE PRECISION[]"))
val metadata = column[JValue]("metadata", O.SqlType("JSONB"))

override def * = (id, createdAt, prices, metadata).mapTo[BaeldungEntity]

}

val baeldungEntityTable = TableQuery[BaeldungEntityTable]

0 comments on commit 9839b0d

Please sign in to comment.