From 58d1d9742a1ecbe0e63d2db0abec390783b49b25 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Mon, 24 Mar 2025 22:13:30 -0400 Subject: [PATCH 01/31] Add support for unix domain sockets --- .../core/shared/src/main/scala/Session.scala | 93 +++++++++++++++---- 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/modules/core/shared/src/main/scala/Session.scala b/modules/core/shared/src/main/scala/Session.scala index 75211551e..ef304c543 100644 --- a/modules/core/shared/src/main/scala/Session.scala +++ b/modules/core/shared/src/main/scala/Session.scala @@ -10,7 +10,8 @@ import cats.effect.std.Console import cats.syntax.all._ import com.comcast.ip4s.{Host, Port, SocketAddress} import fs2.concurrent.Signal -import fs2.io.net.{ Network, Socket, SocketGroup, SocketOption } +import fs2.io.net.{ Network, Socket, SocketOption } +import fs2.io.net.unixsocket.{ UnixSockets, UnixSocketAddress } import fs2.Pipe import fs2.Stream import org.typelevel.otel4s.trace.Tracer @@ -299,8 +300,6 @@ sealed trait Session[F[_]] { */ def closeEvictedPreparedStatements: F[Unit] - - /** * Transform this `Session` by a given `FunctionK`. * @group Transformations @@ -445,6 +444,20 @@ object Session { Recycler(_.execute(Command("RESET ALL", Origin.unknown, Void.codec)).as(true)) } + /** + * Configuration used to connect to a Postgres server via unix domain sockets. + * + * @param unixSockets UnixSockets instance for `F` + * @param address optional explicit address for the Postgres server domain socket + */ + final case class UnixSocketsConfig[F[_]]( + unixSockets: UnixSockets[F], + address: Option[UnixSocketAddress] + ) { + def addressOrDefault(directory: String, port: Int): UnixSocketAddress = + address.getOrElse(UnixSocketAddress(s"${directory}${java.io.File.separator}.s.PGSQL.${port}")) + } + /** * Supports creation of a `Session`. * @@ -457,6 +470,8 @@ object Session { * @param port Postgres server port; defaults to 5432 * @param credentials user and optional password, evaluated for each session; defaults to user "postgres" with no password * @param database database to use; defaults to None and hence whatever user is used to authenticate (e.g. "postgres" when using default user) + * @param unixSocketsConfig if defined, server connection is made via a unix domain socket instead of a network socket; defaults to none + * @param unixSocketsDirectory directory Postgres server uses for unix domain sockets; defaults to /tmp * @param debug whether debug logs should be written to the console; defaults to false * @param typingStrategy typing strategy; defaults to [[TypingStrategy.BuiltinsOnly]] * @param redactionStrategy redaction strategy; defaults to [[RedactionStrategy.OptIn]] @@ -473,6 +488,8 @@ object Session { val port: Int, val credentials: F[Credentials], val database: Option[String], + val unixSocketsConfig: Option[UnixSocketsConfig[F]], + val unixSocketsDirectory: String, val debug: Boolean, val typingStrategy: TypingStrategy, val redactionStrategy: RedactionStrategy, @@ -490,6 +507,8 @@ object Session { port: Int = self.port, credentials: F[Credentials] = self.credentials, database: Option[String] = self.database, + unixSocketsConfig: Option[UnixSocketsConfig[F]] = self.unixSocketsConfig, + unixSocketsDirectory: String = self.unixSocketsDirectory, debug: Boolean = self.debug, typingStrategy: TypingStrategy = self.typingStrategy, redactionStrategy: RedactionStrategy = self.redactionStrategy, @@ -501,7 +520,7 @@ object Session { queryCacheSize: Int = self.queryCacheSize, parseCacheSize: Int = self.parseCacheSize, ): Builder[F] = - new Builder(host, port, credentials, database, debug, typingStrategy, redactionStrategy, ssl, connectionParameters, socketOptions, readTimeout, commandCacheSize, queryCacheSize, parseCacheSize) + new Builder(host, port, credentials, database, unixSocketsConfig, unixSocketsDirectory, debug, typingStrategy, redactionStrategy, ssl, connectionParameters, socketOptions, readTimeout, commandCacheSize, queryCacheSize, parseCacheSize) def withHost(newHost: String): Builder[F] = copy(host = newHost) @@ -521,6 +540,28 @@ object Session { def withUserAndPassword(newUser: String, newPassword: String): Builder[F] = withCredentials(Credentials(newUser, Some(newPassword))) + /** Configures the Postgres directory for unix domain sockets. */ + def withUnixSocketsDirectory(newUnixSocketsDirectory: String): Builder[F] = + copy(unixSocketsDirectory = newUnixSocketsDirectory) + + /** Configures this session for connecting via unix domain sockets using the default path of ${unixSocketsDirectory}/.s.PGSQL.nnnn where nnnn is the port. */ + def withUnixSockets(implicit U: UnixSockets[F]): Builder[F] = + withUnixSocketsConfig(UnixSocketsConfig(U, None)) + + /** Configures this session for connecting via unix domain sockets using the specified path. */ + def withUnixSockets(path: String)(implicit U: UnixSockets[F]): Builder[F] = + withUnixSockets(UnixSocketAddress(path)) + + /** Configures this session for connecting via unix domain sockets using the specified address. */ + def withUnixSockets(address: UnixSocketAddress)(implicit U: UnixSockets[F]): Builder[F] = + withUnixSocketsConfig(UnixSocketsConfig(U, Some(address))) + + def withUnixSocketsConfig(newUnixSocketsConfig: UnixSocketsConfig[F]): Builder[F] = + copy(unixSocketsConfig = Some(newUnixSocketsConfig)) + + def withoutUnixSocketsConfig: Builder[F] = + copy(unixSocketsConfig = None) + def withDatabase(newDatabase: String): Builder[F] = copy(database = Some(newDatabase)) @@ -597,27 +638,38 @@ object Session { for { dc <- Resource.eval(Describe.Cache.empty[F](commandCacheSize, queryCacheSize)) sslOp <- ssl.toSSLNegotiationOptions(if (debug) logger.some else none) - pool <- Pool.ofF({implicit T: Tracer[F] => fromSocketGroup(Network[F], sslOp, dc)}, max)(Recyclers.full) + pool <- Pool.ofF({implicit T: Tracer[F] => sessions(sslOp, dc)}, max)(Recyclers.full) } yield pool } - private def fromSocketGroup( - socketGroup: SocketGroup[F], - sslOptions: Option[SSLNegotiation.Options[F]], - describeCache: Describe.Cache[F] + private def sessions( + sslOptions: Option[SSLNegotiation.Options[F]], + describeCache: Describe.Cache[F] )(implicit T: Tracer[F]): Resource[F, Session[F]] = { - def fail[A](msg: String): Resource[F, A] = - Resource.eval(Temporal[F].raiseError(new SkunkException(message = msg, sql = None))) - - val sockets: Resource[F, Socket[F]] = { - (Host.fromString(host), Port.fromInt(port)) match { - case (Some(validHost), Some(validPort)) => socketGroup.client(SocketAddress(validHost, validPort), socketOptions) - case (None, _) => fail(s"""Hostname: "$host" is not syntactically valid.""") - case (_, None) => fail(s"Port: $port falls out of the allowed range.") - } + val sockets = unixSocketsConfig match { + case None => + + def fail[A](msg: String): Resource[F, A] = + Resource.eval(Temporal[F].raiseError(new SkunkException(message = msg, sql = None))) + + (Host.fromString(host), Port.fromInt(port)) match { + case (Some(validHost), Some(validPort)) => Network[F].client(SocketAddress(validHost, validPort), socketOptions) + case (None, _) => fail(s"""Hostname: "$host" is not syntactically valid.""") + case (_, None) => fail(s"Port: $port falls out of the allowed range.") + } + + case Some(usc) => + usc.unixSockets.client(usc.addressOrDefault(unixSocketsDirectory, port)) } + fromSockets(sockets, sslOptions, describeCache) + } - for { + private def fromSockets( + sockets: Resource[F, Socket[F]], + sslOptions: Option[SSLNegotiation.Options[F]], + describeCache: Describe.Cache[F] + )(implicit T: Tracer[F]): Resource[F, Session[F]] = + for { namer <- Resource.eval(Namer[F]) pc <- Resource.eval(Parse.Cache.empty[F](parseCacheSize)) proto <- Protocol[F](debug, namer, sockets, sslOptions, describeCache, pc, readTimeout, redactionStrategy) @@ -625,7 +677,6 @@ object Session { _ <- Resource.eval(proto.startup(creds.user, database.getOrElse(creds.user), creds.password, connectionParameters)) sess <- Resource.make(fromProtocol(proto, namer, typingStrategy, redactionStrategy))(_ => proto.cleanup) } yield sess - } } /** @@ -654,6 +705,8 @@ object Session { port = 5432, database = None, credentials = Credentials("postgres", None).pure[F], + unixSocketsConfig = None, + unixSocketsDirectory = "/tmp", debug = false, typingStrategy = TypingStrategy.BuiltinsOnly, redactionStrategy = RedactionStrategy.OptIn, From c521068c90a54c714aaf1a4d617eb69da44b7e22 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Tue, 25 Mar 2025 09:59:36 -0400 Subject: [PATCH 02/31] WIP: Attempt to test unix domain socket support --- docker-compose.yml | 10 +++++++++- modules/tests/shared/src/test/scala/StartupTest.scala | 11 +++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2d2317334..376cc5d1c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,6 +46,14 @@ services: POSTGRES_PASSWORD: banana POSTGRES_HOST_AUTH_METHOD: password POSTGRES_INITDB_ARGS: --auth-host=password + # for testing domain sockets + unixsockets: + image: postgres:11 + environment: + POSTGRES_DB: world + POSTGRES_HOST_AUTH_METHOD: trust + volumes: + - ./target/unixsockets:/var/run/postgres # for testing redshift connections redshift: image: guildeducation/docker-amazon-redshift @@ -65,4 +73,4 @@ services: - 4317:4317 - 4318:4318 environment: - COLLECTOR_OTLP_ENABLED: "true" \ No newline at end of file + COLLECTOR_OTLP_ENABLED: "true" diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index 3e0a369ca..f452b53ac 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -239,4 +239,15 @@ class StartupTest extends ffstest.FTest { .single .use(_ => IO.unit).assertFailsWith[UnknownHostException] } + + tracedTest("unix domain sockets - successful login") { implicit tracer: Tracer[IO] => + Session.Builder[IO] + .withUnixSocketsDirectory("target/unixsockets") + .withUnixSockets + .withUserAndPassword("jimmy", "banana") + .withDatabase("world") + .single + .use(_ => IO.unit) + } + } From 7a54717e6e91737f9f65ded39056a2814969b1ae Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Tue, 25 Mar 2025 12:27:59 -0400 Subject: [PATCH 03/31] Try to fix GHA by switching to ubuntu-22.04 image --- .github/workflows/ci.yml | 10 +++++----- build.sbt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08df3e3aa..4f564a6f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: name: Test strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] scala: [2.13, 3] java: [temurin@11] project: [skunkJS, skunkJVM, skunkNative] @@ -69,7 +69,7 @@ jobs: run: sbt githubWorkflowCheck - name: Check headers - if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-22.04' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll - name: scalaJSLink @@ -84,11 +84,11 @@ jobs: run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test - name: Check binary compatibility - if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-22.04' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues - name: Generate API documentation - if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-22.04' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc - name: Make target directories @@ -112,7 +112,7 @@ jobs: if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/series/0.6.x') strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] java: [temurin@11] runs-on: ${{ matrix.os }} steps: diff --git a/build.sbt b/build.sbt index eab646a71..763af940e 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,7 @@ ThisBuild / developers := List( ThisBuild / tlCiReleaseBranches += "series/0.6.x" ThisBuild / tlCiScalafmtCheck := false ThisBuild / tlSitePublishBranch := Some("series/0.6.x") -ThisBuild / githubWorkflowOSes := Seq("ubuntu-latest") +ThisBuild / githubWorkflowOSes := Seq("ubuntu-22.04") ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11")) ThisBuild / tlJdkRelease := Some(8) From 1edf156429a8389950f4b85bd2f39c0486fcc314 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Tue, 25 Mar 2025 12:45:10 -0400 Subject: [PATCH 04/31] Don't use File.separator --- modules/core/shared/src/main/scala/Session.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/shared/src/main/scala/Session.scala b/modules/core/shared/src/main/scala/Session.scala index ef304c543..ba49fb78d 100644 --- a/modules/core/shared/src/main/scala/Session.scala +++ b/modules/core/shared/src/main/scala/Session.scala @@ -455,7 +455,7 @@ object Session { address: Option[UnixSocketAddress] ) { def addressOrDefault(directory: String, port: Int): UnixSocketAddress = - address.getOrElse(UnixSocketAddress(s"${directory}${java.io.File.separator}.s.PGSQL.${port}")) + address.getOrElse(UnixSocketAddress(s"${directory}/.s.PGSQL.${port}")) } /** From 0e8733ff0ea67c673d6526b6d2fb73e26d8f7d2e Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Tue, 25 Mar 2025 13:22:32 -0400 Subject: [PATCH 05/31] Revert GHA base image change --- .github/workflows/ci.yml | 10 +++++----- build.sbt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f564a6f2..08df3e3aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: name: Test strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-latest] scala: [2.13, 3] java: [temurin@11] project: [skunkJS, skunkJVM, skunkNative] @@ -69,7 +69,7 @@ jobs: run: sbt githubWorkflowCheck - name: Check headers - if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-22.04' + if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll - name: scalaJSLink @@ -84,11 +84,11 @@ jobs: run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test - name: Check binary compatibility - if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-22.04' + if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues - name: Generate API documentation - if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-22.04' + if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc - name: Make target directories @@ -112,7 +112,7 @@ jobs: if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/series/0.6.x') strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-latest] java: [temurin@11] runs-on: ${{ matrix.os }} steps: diff --git a/build.sbt b/build.sbt index 763af940e..eab646a71 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,7 @@ ThisBuild / developers := List( ThisBuild / tlCiReleaseBranches += "series/0.6.x" ThisBuild / tlCiScalafmtCheck := false ThisBuild / tlSitePublishBranch := Some("series/0.6.x") -ThisBuild / githubWorkflowOSes := Seq("ubuntu-22.04") +ThisBuild / githubWorkflowOSes := Seq("ubuntu-latest") ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11")) ThisBuild / tlJdkRelease := Some(8) From a4dfb7c796ee99f282eee805fc7d17c93278a962 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Tue, 25 Mar 2025 15:26:59 -0400 Subject: [PATCH 06/31] Bump to JDK 17 --- .github/workflows/ci.yml | 64 ++++++++++++++++++++-------------------- build.sbt | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08df3e3aa..c35ac07ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: matrix: os: [ubuntu-latest] scala: [2.13, 3] - java: [temurin@11] + java: [temurin@17] project: [skunkJS, skunkJVM, skunkNative] runs-on: ${{ matrix.os }} timeout-minutes: 60 @@ -42,17 +42,17 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 - - name: Setup Java (temurin@11) - id: setup-java-temurin-11 - if: matrix.java == 'temurin@11' + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' uses: actions/setup-java@v4 with: distribution: temurin - java-version: 11 + java-version: 17 cache: sbt - name: sbt update - if: matrix.java == 'temurin@11' && steps.setup-java-temurin-11.outputs.cache-hit == 'false' + if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update - name: Start up Postgres @@ -69,7 +69,7 @@ jobs: run: sbt githubWorkflowCheck - name: Check headers - if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll - name: scalaJSLink @@ -84,11 +84,11 @@ jobs: run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test - name: Check binary compatibility - if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues - name: Generate API documentation - if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc - name: Make target directories @@ -113,7 +113,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - java: [temurin@11] + java: [temurin@17] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) @@ -124,17 +124,17 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 - - name: Setup Java (temurin@11) - id: setup-java-temurin-11 - if: matrix.java == 'temurin@11' + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' uses: actions/setup-java@v4 with: distribution: temurin - java-version: 11 + java-version: 17 cache: sbt - name: sbt update - if: matrix.java == 'temurin@11' && steps.setup-java-temurin-11.outputs.cache-hit == 'false' + if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update - name: Start up Postgres @@ -233,7 +233,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04] - java: [temurin@11] + java: [temurin@17] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) @@ -244,17 +244,17 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 - - name: Setup Java (temurin@11) - id: setup-java-temurin-11 - if: matrix.java == 'temurin@11' + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' uses: actions/setup-java@v4 with: distribution: temurin - java-version: 11 + java-version: 17 cache: sbt - name: sbt update - if: matrix.java == 'temurin@11' && steps.setup-java-temurin-11.outputs.cache-hit == 'false' + if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update - name: Start up Postgres @@ -274,7 +274,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04] - java: [temurin@11] + java: [temurin@17] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) @@ -285,17 +285,17 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 - - name: Setup Java (temurin@11) - id: setup-java-temurin-11 - if: matrix.java == 'temurin@11' + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' uses: actions/setup-java@v4 with: distribution: temurin - java-version: 11 + java-version: 17 cache: sbt - name: sbt update - if: matrix.java == 'temurin@11' && steps.setup-java-temurin-11.outputs.cache-hit == 'false' + if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update - name: Start up Postgres @@ -331,17 +331,17 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 - - name: Setup Java (temurin@11) - id: setup-java-temurin-11 - if: matrix.java == 'temurin@11' + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' uses: actions/setup-java@v4 with: distribution: temurin - java-version: 11 + java-version: 17 cache: sbt - name: sbt update - if: matrix.java == 'temurin@11' && steps.setup-java-temurin-11.outputs.cache-hit == 'false' + if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update - name: Start up Postgres diff --git a/build.sbt b/build.sbt index d3ddf9262..96a0fb6c7 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ ThisBuild / tlCiReleaseBranches += "series/0.6.x" ThisBuild / tlCiScalafmtCheck := false ThisBuild / tlSitePublishBranch := Some("series/0.6.x") ThisBuild / githubWorkflowOSes := Seq("ubuntu-latest") -ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11")) +ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17")) ThisBuild / tlJdkRelease := Some(8) ThisBuild / githubWorkflowBuildPreamble ++= nativeBrewInstallWorkflowSteps.value From bdd4becbaab5346119e0d9c003c73bcf47b9dabc Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 09:34:27 -0400 Subject: [PATCH 07/31] Debug tests --- modules/core/shared/src/main/scala/Session.scala | 4 +++- modules/tests/shared/src/test/scala/StartupTest.scala | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/core/shared/src/main/scala/Session.scala b/modules/core/shared/src/main/scala/Session.scala index ba49fb78d..0e60cc0ed 100644 --- a/modules/core/shared/src/main/scala/Session.scala +++ b/modules/core/shared/src/main/scala/Session.scala @@ -659,7 +659,9 @@ object Session { } case Some(usc) => - usc.unixSockets.client(usc.addressOrDefault(unixSocketsDirectory, port)) + val address = usc.addressOrDefault(unixSocketsDirectory, port) + val log = if (debug) Console[F].println(s"using unix socket address ${address}") else Monad[F].unit + Resource.eval(log) *> usc.unixSockets.client(address) } fromSockets(sockets, sslOptions, describeCache) } diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index f452b53ac..29087f6f5 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -241,7 +241,11 @@ class StartupTest extends ffstest.FTest { } tracedTest("unix domain sockets - successful login") { implicit tracer: Tracer[IO] => + println("=== CONTENTS ===") + println("================") + fs2.io.file.Files[IO].list(fs2.io.file.Path("target/unixsockets")).foreach(IO.println).compile.drain >> Session.Builder[IO] + .withDebug(true) .withUnixSocketsDirectory("target/unixsockets") .withUnixSockets .withUserAndPassword("jimmy", "banana") From ba0777a9dd9d46f1ff6fb5e20676d5cd024b7ae8 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 09:44:29 -0400 Subject: [PATCH 08/31] Debug --- docker-compose.yml | 2 +- modules/tests/shared/src/test/scala/StartupTest.scala | 4 ++-- test-unix-socket/placeholder | 0 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 test-unix-socket/placeholder diff --git a/docker-compose.yml b/docker-compose.yml index 376cc5d1c..8954b4594 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,7 @@ services: POSTGRES_DB: world POSTGRES_HOST_AUTH_METHOD: trust volumes: - - ./target/unixsockets:/var/run/postgres + - ./test-unix-socket:/var/run/postgres # for testing redshift connections redshift: image: guildeducation/docker-amazon-redshift diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index 29087f6f5..8451c2b77 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -243,10 +243,10 @@ class StartupTest extends ffstest.FTest { tracedTest("unix domain sockets - successful login") { implicit tracer: Tracer[IO] => println("=== CONTENTS ===") println("================") - fs2.io.file.Files[IO].list(fs2.io.file.Path("target/unixsockets")).foreach(IO.println).compile.drain >> + fs2.io.file.Files[IO].list(fs2.io.file.Path("test-unix-socket")).foreach(IO.println).compile.drain >> Session.Builder[IO] .withDebug(true) - .withUnixSocketsDirectory("target/unixsockets") + .withUnixSocketsDirectory("test-unix-socket") .withUnixSockets .withUserAndPassword("jimmy", "banana") .withDatabase("world") diff --git a/test-unix-socket/placeholder b/test-unix-socket/placeholder new file mode 100644 index 000000000..e69de29bb From f8c03e298d5db3484f70c122c183af16dd129449 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 09:51:55 -0400 Subject: [PATCH 09/31] Debug --- modules/tests/shared/src/test/scala/StartupTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index 8451c2b77..68686fdf0 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -243,7 +243,7 @@ class StartupTest extends ffstest.FTest { tracedTest("unix domain sockets - successful login") { implicit tracer: Tracer[IO] => println("=== CONTENTS ===") println("================") - fs2.io.file.Files[IO].list(fs2.io.file.Path("test-unix-socket")).foreach(IO.println).compile.drain >> + fs2.io.file.Files[IO].list(fs2.io.file.Path(".")).foreach(IO.println).compile.drain >> Session.Builder[IO] .withDebug(true) .withUnixSocketsDirectory("test-unix-socket") From 35c387bb4da8fdbe976130472a8ed6f36202ae43 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 12:36:13 -0400 Subject: [PATCH 10/31] Debug --- build.sbt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 96a0fb6c7..7bc6a7baf 100644 --- a/build.sbt +++ b/build.sbt @@ -56,6 +56,7 @@ ThisBuild / mimaBinaryIssueFilters ++= List( ProblemFilters.exclude[DirectMissingMethodProblem]("skunk.net.BitVectorSocket.fromSocket") ) + // This is used in a couple places lazy val fs2Version = "3.12.0" lazy val openTelemetryVersion = "1.44.1" @@ -199,7 +200,8 @@ lazy val tests = crossProject(JVMPlatform, JSPlatform, NativePlatform) if(System.getProperty("os.arch").startsWith("aarch64")) { Tests.Argument(TestFrameworks.MUnit, "--exclude-tags=X86ArchOnly") } else Tests.Argument() - } + }, + Test / baseDirectory := (ThisBuild / Test / run / baseDirectory).value ) .jvmSettings( Test / fork := true, From 7b0df14694190e5ae7f2a0f53895b0666f6d4783 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 12:40:31 -0400 Subject: [PATCH 11/31] Debug --- modules/tests/shared/src/test/scala/StartupTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index 68686fdf0..bd1e96564 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -242,8 +242,8 @@ class StartupTest extends ffstest.FTest { tracedTest("unix domain sockets - successful login") { implicit tracer: Tracer[IO] => println("=== CONTENTS ===") + fs2.io.file.Files[IO].list(fs2.io.file.Path("test-unix-socket")).foreach(IO.println).compile.drain >> println("================") - fs2.io.file.Files[IO].list(fs2.io.file.Path(".")).foreach(IO.println).compile.drain >> Session.Builder[IO] .withDebug(true) .withUnixSocketsDirectory("test-unix-socket") From d8c49895f8004a6a6299ad3d360aa06eacaa77ce Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 12:42:59 -0400 Subject: [PATCH 12/31] Debug --- modules/tests/shared/src/test/scala/StartupTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index bd1e96564..54732d9f8 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -243,7 +243,7 @@ class StartupTest extends ffstest.FTest { tracedTest("unix domain sockets - successful login") { implicit tracer: Tracer[IO] => println("=== CONTENTS ===") fs2.io.file.Files[IO].list(fs2.io.file.Path("test-unix-socket")).foreach(IO.println).compile.drain >> - println("================") + IO.println("================") >> Session.Builder[IO] .withDebug(true) .withUnixSocketsDirectory("test-unix-socket") From e5d258c2464c00098db9b213346169a3423789e6 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 12:51:21 -0400 Subject: [PATCH 13/31] Debug --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8954b4594..f3c189055 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,7 @@ services: POSTGRES_DB: world POSTGRES_HOST_AUTH_METHOD: trust volumes: - - ./test-unix-socket:/var/run/postgres + - ./test-unix-socket:/var/run/postgresql # for testing redshift connections redshift: image: guildeducation/docker-amazon-redshift From 5c52e14dab15eaa061758c07a54f23e6887403d2 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 12:55:08 -0400 Subject: [PATCH 14/31] Debug --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index f3c189055..4e4d400a3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,6 +51,7 @@ services: image: postgres:11 environment: POSTGRES_DB: world + POSTGRES_USER: jimmy POSTGRES_HOST_AUTH_METHOD: trust volumes: - ./test-unix-socket:/var/run/postgresql From bc2d3368c3050c9ad4f03710cad82c23b792b59d Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 13:07:12 -0400 Subject: [PATCH 15/31] Ignore exception when terminating socket --- .../scala/net/BufferedMessageSocket.scala | 4 ++-- .../shared/src/main/scala/net/Protocol.scala | 19 +++++++------------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/modules/core/shared/src/main/scala/net/BufferedMessageSocket.scala b/modules/core/shared/src/main/scala/net/BufferedMessageSocket.scala index 6dde0ec4e..b0f9802e6 100644 --- a/modules/core/shared/src/main/scala/net/BufferedMessageSocket.scala +++ b/modules/core/shared/src/main/scala/net/BufferedMessageSocket.scala @@ -164,8 +164,8 @@ object BufferedMessageSocket { noTop.subscribeAwait(maxQueued) override protected def terminate: F[Unit] = - fib.cancel *> // stop processing incoming messages - send(Terminate) // server will close the socket when it sees this + fib.cancel *> // stop processing incoming messages + send(Terminate).attempt.void // server will close the socket when it sees this override def history(max: Int): F[List[Either[Any, Any]]] = ms.history(max) diff --git a/modules/core/shared/src/main/scala/net/Protocol.scala b/modules/core/shared/src/main/scala/net/Protocol.scala index 00794b2ea..4b57c7bfb 100644 --- a/modules/core/shared/src/main/scala/net/Protocol.scala +++ b/modules/core/shared/src/main/scala/net/Protocol.scala @@ -208,19 +208,14 @@ object Protocol { def execute(maxRows: Int): F[List[B] ~ Boolean] } - /** - * Resource yielding a new `Protocol` with the given `host` and `port`. - * @param host Postgres server host - * @param port Postgres port, default 5432 - */ def apply[F[_]: Temporal: Tracer: Console]( - debug: Boolean, - nam: Namer[F], - sockets: Resource[F, Socket[F]], - sslOptions: Option[SSLNegotiation.Options[F]], - describeCache: Describe.Cache[F], - parseCache: Parse.Cache[F], - readTimeout: Duration, + debug: Boolean, + nam: Namer[F], + sockets: Resource[F, Socket[F]], + sslOptions: Option[SSLNegotiation.Options[F]], + describeCache: Describe.Cache[F], + parseCache: Parse.Cache[F], + readTimeout: Duration, redactionStrategy: RedactionStrategy ): Resource[F, Protocol[F]] = for { From 67993c19808d9293c1fd0190d4954c26c4cbfbaf Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 13:15:14 -0400 Subject: [PATCH 16/31] Ensure site coverage runs with J17 --- .github/workflows/ci.yml | 12 ++++++------ build.sbt | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c35ac07ff..e012fe017 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: name: Test strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] scala: [2.13, 3] java: [temurin@17] project: [skunkJS, skunkJVM, skunkNative] @@ -69,7 +69,7 @@ jobs: run: sbt githubWorkflowCheck - name: Check headers - if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll - name: scalaJSLink @@ -84,11 +84,11 @@ jobs: run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test - name: Check binary compatibility - if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues - name: Generate API documentation - if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc - name: Make target directories @@ -112,7 +112,7 @@ jobs: if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/series/0.6.x') strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] java: [temurin@17] runs-on: ${{ matrix.os }} steps: @@ -320,7 +320,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04] - java: [temurin@11] + java: [temurin@17] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) diff --git a/build.sbt b/build.sbt index 7bc6a7baf..2e38aa2bd 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,6 @@ ThisBuild / developers := List( ThisBuild / tlCiReleaseBranches += "series/0.6.x" ThisBuild / tlCiScalafmtCheck := false ThisBuild / tlSitePublishBranch := Some("series/0.6.x") -ThisBuild / githubWorkflowOSes := Seq("ubuntu-latest") ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17")) ThisBuild / tlJdkRelease := Some(8) @@ -38,6 +37,7 @@ ThisBuild / githubWorkflowAddedJobs += id = "coverage", name = s"Generate coverage report (2.13 JVM only)", scalas = Nil, + javas = githubWorkflowJavaVersions.value.toList, sbtStepPreamble = Nil, steps = githubWorkflowJobSetup.value.toList ++ List( From eae043c6563a3fa20f9d72b3ebcb460a6e67eccb Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 13:54:26 -0400 Subject: [PATCH 17/31] Exclude unix domain socket test from Mac and Windows builds --- build.sbt | 11 ++++++++--- modules/tests/shared/src/test/scala/StartupTest.scala | 9 +++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 2e38aa2bd..15ec2bcb1 100644 --- a/build.sbt +++ b/build.sbt @@ -197,9 +197,14 @@ lazy val tests = crossProject(JVMPlatform, JSPlatform, NativePlatform) ), testFrameworks += new TestFramework("munit.Framework"), testOptions += { - if(System.getProperty("os.arch").startsWith("aarch64")) { - Tests.Argument(TestFrameworks.MUnit, "--exclude-tags=X86ArchOnly") - } else Tests.Argument() + var excludedTags = List.empty[String] + if (System.getProperty("os.arch").startsWith("aarch64")) + excludedTags = "X86ArchOnly" :: excludedTags + if (!System.getProperty("os.name").contains("linux")) + excludedTags = "LinuxOnly" :: excludedTags + if (excludedTags.nonEmpty) + Tests.Argument(TestFrameworks.MUnit, "--exclude-tags=" + excludedTags.mkString(",")) + else Tests.Argument() }, Test / baseDirectory := (ThisBuild / Test / run / baseDirectory).value ) diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index 54732d9f8..f2140669d 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -240,12 +240,10 @@ class StartupTest extends ffstest.FTest { .use(_ => IO.unit).assertFailsWith[UnknownHostException] } - tracedTest("unix domain sockets - successful login") { implicit tracer: Tracer[IO] => - println("=== CONTENTS ===") - fs2.io.file.Files[IO].list(fs2.io.file.Path("test-unix-socket")).foreach(IO.println).compile.drain >> - IO.println("================") >> + object LinuxOnly extends munit.Tag("LinuxOnly") + + tracedTest("unix domain sockets - successful login".tag(LinuxOnly)) { implicit tracer: Tracer[IO] => Session.Builder[IO] - .withDebug(true) .withUnixSocketsDirectory("test-unix-socket") .withUnixSockets .withUserAndPassword("jimmy", "banana") @@ -253,5 +251,4 @@ class StartupTest extends ffstest.FTest { .single .use(_ => IO.unit) } - } From 7ca500bce3404052f178252b54d522a9083fb1fd Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 15:03:00 -0400 Subject: [PATCH 18/31] Cleanup --- docker-compose.yml | 2 +- .../core/shared/src/main/scala/net/BufferedMessageSocket.scala | 2 +- test-unix-socket/placeholder | 0 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 test-unix-socket/placeholder diff --git a/docker-compose.yml b/docker-compose.yml index 4e4d400a3..2e13aded8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -52,7 +52,7 @@ services: environment: POSTGRES_DB: world POSTGRES_USER: jimmy - POSTGRES_HOST_AUTH_METHOD: trust + POSTGRES_HOST_AUTH_METHOD: peer volumes: - ./test-unix-socket:/var/run/postgresql # for testing redshift connections diff --git a/modules/core/shared/src/main/scala/net/BufferedMessageSocket.scala b/modules/core/shared/src/main/scala/net/BufferedMessageSocket.scala index b0f9802e6..e05da2fb6 100644 --- a/modules/core/shared/src/main/scala/net/BufferedMessageSocket.scala +++ b/modules/core/shared/src/main/scala/net/BufferedMessageSocket.scala @@ -165,7 +165,7 @@ object BufferedMessageSocket { override protected def terminate: F[Unit] = fib.cancel *> // stop processing incoming messages - send(Terminate).attempt.void // server will close the socket when it sees this + send(Terminate).attempt.void // server will close the socket when it sees this; ignore failure as socket may be closed mid-write override def history(max: Int): F[List[Either[Any, Any]]] = ms.history(max) diff --git a/test-unix-socket/placeholder b/test-unix-socket/placeholder deleted file mode 100644 index e69de29bb..000000000 From 374c70debf10acbabb4f3a265f451f081a1457cd Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 5 Apr 2025 18:34:22 -0400 Subject: [PATCH 19/31] Cleanup --- docker-compose.yml | 2 +- modules/tests/shared/src/test/scala/StartupTest.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2e13aded8..35a3e6945 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -52,7 +52,7 @@ services: environment: POSTGRES_DB: world POSTGRES_USER: jimmy - POSTGRES_HOST_AUTH_METHOD: peer + POSTGRES_PASSWORD: banana volumes: - ./test-unix-socket:/var/run/postgresql # for testing redshift connections diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index f2140669d..7aa1f6efb 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -246,7 +246,7 @@ class StartupTest extends ffstest.FTest { Session.Builder[IO] .withUnixSocketsDirectory("test-unix-socket") .withUnixSockets - .withUserAndPassword("jimmy", "banana") + .withUser("jimmy") .withDatabase("world") .single .use(_ => IO.unit) From 07661bc4240acf35e7bbeb4d9e57a699b11dc2d3 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Wed, 23 Apr 2025 08:09:47 -0400 Subject: [PATCH 20/31] Update to fs2 3.13.0-M1 --- build.sbt | 2 +- .../core/shared/src/main/scala/Session.scala | 138 +++++++++--------- .../shared/src/test/scala/StartupTest.scala | 3 +- 3 files changed, 72 insertions(+), 71 deletions(-) diff --git a/build.sbt b/build.sbt index 15ec2bcb1..e56a4ae10 100644 --- a/build.sbt +++ b/build.sbt @@ -58,7 +58,7 @@ ThisBuild / mimaBinaryIssueFilters ++= List( // This is used in a couple places -lazy val fs2Version = "3.12.0" +lazy val fs2Version = "3.13.0-M1" lazy val openTelemetryVersion = "1.44.1" lazy val otel4sVersion = "0.11.1" lazy val refinedVersion = "0.11.0" diff --git a/modules/core/shared/src/main/scala/Session.scala b/modules/core/shared/src/main/scala/Session.scala index 0e60cc0ed..09ff891e3 100644 --- a/modules/core/shared/src/main/scala/Session.scala +++ b/modules/core/shared/src/main/scala/Session.scala @@ -8,10 +8,9 @@ import cats._ import cats.effect._ import cats.effect.std.Console import cats.syntax.all._ -import com.comcast.ip4s.{Host, Port, SocketAddress} +import com.comcast.ip4s.* import fs2.concurrent.Signal import fs2.io.net.{ Network, Socket, SocketOption } -import fs2.io.net.unixsocket.{ UnixSockets, UnixSocketAddress } import fs2.Pipe import fs2.Stream import org.typelevel.otel4s.trace.Tracer @@ -22,7 +21,6 @@ import skunk.util._ import skunk.net.SSLNegotiation import skunk.data.TransactionIsolationLevel import skunk.data.TransactionAccessMode -import skunk.exception.SkunkException import skunk.net.protocol.Describe import scala.concurrent.duration.Duration import skunk.net.protocol.Parse @@ -444,18 +442,13 @@ object Session { Recycler(_.execute(Command("RESET ALL", Origin.unknown, Void.codec)).as(true)) } - /** - * Configuration used to connect to a Postgres server via unix domain sockets. - * - * @param unixSockets UnixSockets instance for `F` - * @param address optional explicit address for the Postgres server domain socket - */ - final case class UnixSocketsConfig[F[_]]( - unixSockets: UnixSockets[F], - address: Option[UnixSocketAddress] - ) { - def addressOrDefault(directory: String, port: Int): UnixSocketAddress = - address.getOrElse(UnixSocketAddress(s"${directory}/.s.PGSQL.${port}")) + /** Enumeration of protocols that can be used to connect to a Postgres server. */ + sealed trait ConnectionType + object ConnectionType { + /** Connect via TCP using a host and port. */ + case object TCP extends ConnectionType + /** Connect via a Unix domain socket. */ + case object Unix extends ConnectionType } /** @@ -466,12 +459,13 @@ object Session { * After overriding the various defaults, call `single` to create a single-use session or `pooled` * to create a pool of sessions. * + * @param connectionType type of connection to use to connect to server; defaults to TCP * @param host Postgres server host; defaults to localhost * @param port Postgres server port; defaults to 5432 * @param credentials user and optional password, evaluated for each session; defaults to user "postgres" with no password * @param database database to use; defaults to None and hence whatever user is used to authenticate (e.g. "postgres" when using default user) - * @param unixSocketsConfig if defined, server connection is made via a unix domain socket instead of a network socket; defaults to none - * @param unixSocketsDirectory directory Postgres server uses for unix domain sockets; defaults to /tmp + * @param unixSocketAddress explicit path to the Postgres server unix domain socket; if not defined and connection type is Unix, defaults to ${unixSocketsDirectory}/.s.PGSQL.nnnn where nnnn is the port + * @param unixSocketDirectory directory Postgres server uses for unix domain sockets; defaults to /tmp * @param debug whether debug logs should be written to the console; defaults to false * @param typingStrategy typing strategy; defaults to [[TypingStrategy.BuiltinsOnly]] * @param redactionStrategy redaction strategy; defaults to [[RedactionStrategy.OptIn]] @@ -484,12 +478,13 @@ object Session { * @param parseCacheSize size of the pool-level cache for parsing statements; defaults to 2048 */ final class Builder[F[_]: Temporal: Network: Console] private ( - val host: String, - val port: Int, + val connectionType: ConnectionType, + val host: Host, + val port: Port, + val unixSocketAddress: Option[UnixSocketAddress], + val unixSocketDirectory: String, val credentials: F[Credentials], val database: Option[String], - val unixSocketsConfig: Option[UnixSocketsConfig[F]], - val unixSocketsDirectory: String, val debug: Boolean, val typingStrategy: TypingStrategy, val redactionStrategy: RedactionStrategy, @@ -503,12 +498,13 @@ object Session { ) { self => private def copy( - host: String = self.host, - port: Int = self.port, + connectionType: ConnectionType = self.connectionType, + host: Host = self.host, + port: Port = self.port, + unixSocketAddress: Option[UnixSocketAddress] = self.unixSocketAddress, + unixSocketDirectory: String = self.unixSocketDirectory, credentials: F[Credentials] = self.credentials, database: Option[String] = self.database, - unixSocketsConfig: Option[UnixSocketsConfig[F]] = self.unixSocketsConfig, - unixSocketsDirectory: String = self.unixSocketsDirectory, debug: Boolean = self.debug, typingStrategy: TypingStrategy = self.typingStrategy, redactionStrategy: RedactionStrategy = self.redactionStrategy, @@ -520,14 +516,48 @@ object Session { queryCacheSize: Int = self.queryCacheSize, parseCacheSize: Int = self.parseCacheSize, ): Builder[F] = - new Builder(host, port, credentials, database, unixSocketsConfig, unixSocketsDirectory, debug, typingStrategy, redactionStrategy, ssl, connectionParameters, socketOptions, readTimeout, commandCacheSize, queryCacheSize, parseCacheSize) + new Builder(connectionType, host, port, unixSocketAddress, unixSocketDirectory, credentials, database, debug, typingStrategy, redactionStrategy, ssl, connectionParameters, socketOptions, readTimeout, commandCacheSize, queryCacheSize, parseCacheSize) + /** Configures the connection type. */ + def withConnectionType(newConnectionType: ConnectionType): Builder[F] = + copy(connectionType = newConnectionType) + + /** Configures the host of the Postgres server. Throws `IllegalArgumentException` if the specified host is not syntactically valid. */ def withHost(newHost: String): Builder[F] = + withHost(Host.fromString(newHost).getOrElse(throw new IllegalArgumentException(s"""Hostname: "$host" is not syntactically valid."""))) + + /** Configures the host of the Postgres server. */ + def withHost(newHost: Host): Builder[F] = copy(host = newHost) + /** Configures the post of the Postgres server. Throws `IllegalArgumentException` if the specified port is not a valid port number. */ def withPort(newPort: Int): Builder[F] = + withPort(Port.fromInt(newPort).getOrElse(throw new IllegalArgumentException(s"Port: $port falls out of the allowed range."))) + + /** Configures the port of the Postgres server. */ + def withPort(newPort: Port): Builder[F] = copy(port = newPort) + /** Configures this session for connecting via unix domain sockets. */ + def withUnixSockets: Builder[F] = + copy(connectionType = ConnectionType.Unix) + + /** Configures the Postgres directory for unix domain sockets. */ + def withUnixSocketDirectory(newUnixSocketDirectory: String): Builder[F] = + withUnixSockets.copy(unixSocketDirectory = newUnixSocketDirectory) + + /** Configures this session for connecting via unix domain sockets using the specified path. */ + def withUnixSocketAddress(path: String): Builder[F] = + withUnixSockets.withUnixSocketAddress(UnixSocketAddress(path)) + + /** Configures this session for connecting via unix domain sockets using the specified address. */ + def withUnixSocketAddress(newUnixSocketAddress: UnixSocketAddress): Builder[F] = + withUnixSockets.copy(unixSocketAddress = Some(newUnixSocketAddress)) + + /** Clears the explicitly configured unix socket address. */ + def withoutUnixSocketAddress: Builder[F] = + copy(unixSocketAddress = None) + def withCredentials(newCredentials: F[Credentials]): Builder[F] = copy(credentials = newCredentials) @@ -540,28 +570,6 @@ object Session { def withUserAndPassword(newUser: String, newPassword: String): Builder[F] = withCredentials(Credentials(newUser, Some(newPassword))) - /** Configures the Postgres directory for unix domain sockets. */ - def withUnixSocketsDirectory(newUnixSocketsDirectory: String): Builder[F] = - copy(unixSocketsDirectory = newUnixSocketsDirectory) - - /** Configures this session for connecting via unix domain sockets using the default path of ${unixSocketsDirectory}/.s.PGSQL.nnnn where nnnn is the port. */ - def withUnixSockets(implicit U: UnixSockets[F]): Builder[F] = - withUnixSocketsConfig(UnixSocketsConfig(U, None)) - - /** Configures this session for connecting via unix domain sockets using the specified path. */ - def withUnixSockets(path: String)(implicit U: UnixSockets[F]): Builder[F] = - withUnixSockets(UnixSocketAddress(path)) - - /** Configures this session for connecting via unix domain sockets using the specified address. */ - def withUnixSockets(address: UnixSocketAddress)(implicit U: UnixSockets[F]): Builder[F] = - withUnixSocketsConfig(UnixSocketsConfig(U, Some(address))) - - def withUnixSocketsConfig(newUnixSocketsConfig: UnixSocketsConfig[F]): Builder[F] = - copy(unixSocketsConfig = Some(newUnixSocketsConfig)) - - def withoutUnixSocketsConfig: Builder[F] = - copy(unixSocketsConfig = None) - def withDatabase(newDatabase: String): Builder[F] = copy(database = Some(newDatabase)) @@ -646,22 +654,15 @@ object Session { sslOptions: Option[SSLNegotiation.Options[F]], describeCache: Describe.Cache[F] )(implicit T: Tracer[F]): Resource[F, Session[F]] = { - val sockets = unixSocketsConfig match { - case None => - - def fail[A](msg: String): Resource[F, A] = - Resource.eval(Temporal[F].raiseError(new SkunkException(message = msg, sql = None))) - - (Host.fromString(host), Port.fromInt(port)) match { - case (Some(validHost), Some(validPort)) => Network[F].client(SocketAddress(validHost, validPort), socketOptions) - case (None, _) => fail(s"""Hostname: "$host" is not syntactically valid.""") - case (_, None) => fail(s"Port: $port falls out of the allowed range.") - } - - case Some(usc) => - val address = usc.addressOrDefault(unixSocketsDirectory, port) - val log = if (debug) Console[F].println(s"using unix socket address ${address}") else Monad[F].unit - Resource.eval(log) *> usc.unixSockets.client(address) + val sockets = connectionType match { + case ConnectionType.TCP => + val address = SocketAddress(host, port) + Network[F].connect(address, socketOptions) + + case ConnectionType.Unix => + val address = unixSocketAddress.getOrElse(UnixSocketAddress(s"${unixSocketDirectory}/.s.PGSQL.${port}")) + val filteredSocketOptions = socketOptions.filter(o => o.key != SocketOption.NoDelay) + Network[F].connect(address, filteredSocketOptions) } fromSockets(sockets, sslOptions, describeCache) } @@ -703,12 +704,13 @@ object Session { object Builder { def apply[F[_]: Temporal: Network: Console]: Builder[F] = new Builder[F]( - host = "localhost", - port = 5432, + connectionType = ConnectionType.TCP, + host = host"localhost", + port = port"5432", + unixSocketAddress = None, + unixSocketDirectory = "/tmp", database = None, credentials = Credentials("postgres", None).pure[F], - unixSocketsConfig = None, - unixSocketsDirectory = "/tmp", debug = false, typingStrategy = TypingStrategy.BuiltinsOnly, redactionStrategy = RedactionStrategy.OptIn, diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index 7aa1f6efb..62a6ac5ed 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -244,8 +244,7 @@ class StartupTest extends ffstest.FTest { tracedTest("unix domain sockets - successful login".tag(LinuxOnly)) { implicit tracer: Tracer[IO] => Session.Builder[IO] - .withUnixSocketsDirectory("test-unix-socket") - .withUnixSockets + .withUnixSocketDirectory("test-unix-socket") .withUser("jimmy") .withDatabase("world") .single From 968395f5669bbe38955a79a1805b41148f507821 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Wed, 23 Apr 2025 09:39:06 -0400 Subject: [PATCH 21/31] Adjust exceptions thrown from host/port conversions --- modules/core/shared/src/main/scala/Session.scala | 5 +++-- .../tests/shared/src/test/scala/SessionTest.scala | 14 +++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/core/shared/src/main/scala/Session.scala b/modules/core/shared/src/main/scala/Session.scala index 09ff891e3..ed12fb827 100644 --- a/modules/core/shared/src/main/scala/Session.scala +++ b/modules/core/shared/src/main/scala/Session.scala @@ -16,6 +16,7 @@ import fs2.Stream import org.typelevel.otel4s.trace.Tracer import skunk.codec.all.bool import skunk.data._ +import skunk.exception.SkunkException import skunk.net.Protocol import skunk.util._ import skunk.net.SSLNegotiation @@ -524,7 +525,7 @@ object Session { /** Configures the host of the Postgres server. Throws `IllegalArgumentException` if the specified host is not syntactically valid. */ def withHost(newHost: String): Builder[F] = - withHost(Host.fromString(newHost).getOrElse(throw new IllegalArgumentException(s"""Hostname: "$host" is not syntactically valid."""))) + withHost(Host.fromString(newHost).getOrElse(throw new SkunkException(sql = None, message = s"""Hostname: "$newHost" is not syntactically valid."""))) /** Configures the host of the Postgres server. */ def withHost(newHost: Host): Builder[F] = @@ -532,7 +533,7 @@ object Session { /** Configures the post of the Postgres server. Throws `IllegalArgumentException` if the specified port is not a valid port number. */ def withPort(newPort: Int): Builder[F] = - withPort(Port.fromInt(newPort).getOrElse(throw new IllegalArgumentException(s"Port: $port falls out of the allowed range."))) + withPort(Port.fromInt(newPort).getOrElse(throw new SkunkException(sql = None, message = s"Port: $newPort falls out of the allowed range."))) /** Configures the port of the Postgres server. */ def withPort(newPort: Port): Builder[F] = diff --git a/modules/tests/shared/src/test/scala/SessionTest.scala b/modules/tests/shared/src/test/scala/SessionTest.scala index b0e87e2f4..27e693dec 100644 --- a/modules/tests/shared/src/test/scala/SessionTest.scala +++ b/modules/tests/shared/src/test/scala/SessionTest.scala @@ -12,12 +12,16 @@ import skunk.exception.SkunkException class SessionTest extends ffstest.FTest { test("Invalid host") { - Session.Builder[IO].withHost("").single.use(_ => IO.unit).assertFailsWith[SkunkException] - .flatMap(e => assertEqual("message", e.message, """Hostname: "" is not syntactically valid.""")) + val e = intercept[SkunkException] { + Session.Builder[IO].withHost("") + } + assertEquals(e.message, """Hostname: "" is not syntactically valid.""") } + test("Invalid port") { - Session.Builder[IO].withPort(-1).single.use(_ => IO.unit).assertFailsWith[SkunkException] - .flatMap(e => assertEqual("message", e.message, "Port: -1 falls out of the allowed range.")) + val e = intercept[SkunkException] { + Session.Builder[IO].withPort(-1).single.use(_ => IO.unit) + } + assertEquals(e.message, "Port: -1 falls out of the allowed range.") } - } From ffeee5cb758157097550ddbba1fc4e73c184c201 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Wed, 23 Apr 2025 16:45:43 -0400 Subject: [PATCH 22/31] Bump to fs2 3.13.0-M2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e56a4ae10..a93c1b12f 100644 --- a/build.sbt +++ b/build.sbt @@ -58,7 +58,7 @@ ThisBuild / mimaBinaryIssueFilters ++= List( // This is used in a couple places -lazy val fs2Version = "3.13.0-M1" +lazy val fs2Version = "3.12.0-M2" lazy val openTelemetryVersion = "1.44.1" lazy val otel4sVersion = "0.11.1" lazy val refinedVersion = "0.11.0" From ce8b2d921d2fe8cb81c1a0e3bdbef27a49dfe0c6 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Wed, 23 Apr 2025 16:56:49 -0400 Subject: [PATCH 23/31] Bump to fs2 3.13.0-M2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a93c1b12f..ace867707 100644 --- a/build.sbt +++ b/build.sbt @@ -58,7 +58,7 @@ ThisBuild / mimaBinaryIssueFilters ++= List( // This is used in a couple places -lazy val fs2Version = "3.12.0-M2" +lazy val fs2Version = "3.13.0-M2" lazy val openTelemetryVersion = "1.44.1" lazy val otel4sVersion = "0.11.1" lazy val refinedVersion = "0.11.0" From 5b682260103d1f497e3b75f4ae12e0d89082ff4a Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Thu, 26 Jun 2025 20:44:17 -0400 Subject: [PATCH 24/31] Update to latest fs2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ace867707..21340f595 100644 --- a/build.sbt +++ b/build.sbt @@ -58,7 +58,7 @@ ThisBuild / mimaBinaryIssueFilters ++= List( // This is used in a couple places -lazy val fs2Version = "3.13.0-M2" +lazy val fs2Version = "3.13.0-M5" lazy val openTelemetryVersion = "1.44.1" lazy val otel4sVersion = "0.11.1" lazy val refinedVersion = "0.11.0" From b51f59cd89e37beb786a2438b801db35db38976f Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Mon, 15 Sep 2025 15:14:28 +0100 Subject: [PATCH 25/31] Prepare for Native 0.5 - update dependencies --- build.sbt | 38 +++++++++---------- .../net/message/PasswordMessagePlatform.scala | 8 ++-- .../scala/net/message/ScramPlatform.scala | 4 +- .../docs/src/main/scala/mdocext/Main.scala | 2 +- .../docs/src/main/scala/mdocext/package.scala | 2 +- project/plugins.sbt | 4 +- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/build.sbt b/build.sbt index 21d4e353a..3fe3f4f65 100644 --- a/build.sbt +++ b/build.sbt @@ -19,7 +19,7 @@ ThisBuild / tlSitePublishBranch := Some("series/0.6.x") ThisBuild / githubWorkflowOSes := Seq("ubuntu-latest") ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11")) ThisBuild / tlJdkRelease := Some(8) - +ThisBuild / resolvers += Resolver.sonatypeCentralSnapshots ThisBuild / githubWorkflowBuildPreamble ++= nativeBrewInstallWorkflowSteps.value ThisBuild / nativeBrewInstallCond := Some("matrix.project == 'skunkNative'") @@ -59,10 +59,10 @@ ThisBuild / mimaBinaryIssueFilters ++= List( ThisBuild / tlFatalWarnings := false // This is used in a couple places -lazy val fs2Version = "3.12.2" +lazy val fs2Version = "3.13.0-M7" lazy val openTelemetryVersion = "1.52.0" -lazy val otel4sVersion = "0.13.1" -lazy val refinedVersion = "0.11.0" +lazy val otel4sVersion = "0.14-eadbb3d-SNAPSHOT" +lazy val refinedVersion = "0.11.3" // Global Settings lazy val commonSettings = Seq( @@ -107,16 +107,16 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) name := "skunk-core", description := "Tagless, non-blocking data access library for Postgres.", libraryDependencies ++= Seq( - "org.typelevel" %%% "cats-core" % "2.11.0", - "org.typelevel" %%% "cats-effect" % "3.6.3", + "org.typelevel" %%% "cats-core" % "2.13.0", + "org.typelevel" %%% "cats-effect" % "3.7.0-RC1", "co.fs2" %%% "fs2-core" % fs2Version, "co.fs2" %%% "fs2-io" % fs2Version, - "org.scodec" %%% "scodec-bits" % "1.1.38", - "org.scodec" %%% "scodec-core" % (if (tlIsScala3.value) "2.2.2" else "1.11.10"), - "org.scodec" %%% "scodec-cats" % "1.2.0", + "org.scodec" %%% "scodec-bits" % "1.2.4", + "org.scodec" %%% "scodec-core" % (if (tlIsScala3.value) "2.3.3" else "1.11.11"), + "org.scodec" %%% "scodec-cats" % "1.3.0-RC1", "org.typelevel" %%% "otel4s-core-trace" % otel4sVersion, - "org.tpolecat" %%% "sourcepos" % "1.1.0", - "org.typelevel" %%% "twiddles-core" % "0.8.0", + "org.tpolecat" %%% "sourcepos" % "1.2.0", + "org.typelevel" %%% "twiddles-core" % "0.9.0", ) ++ Seq( "com.beachape" %%% "enumeratum" % "1.7.4", ).filterNot(_ => tlIsScala3.value) @@ -124,9 +124,9 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) libraryDependencies += "com.ongres.scram" % "client" % "2.1", ).platformsSettings(JSPlatform, NativePlatform)( libraryDependencies ++= Seq( - "com.armanbilge" %%% "saslprep" % "0.1.1", - "io.github.cquiroz" %%% "scala-java-time" % "2.5.0", - "io.github.cquiroz" %%% "locales-minimal-en_us-db" % "1.5.3" + "com.armanbilge" %%% "saslprep" % "0.1.2", + "io.github.cquiroz" %%% "scala-java-time" % "2.6.0", + "io.github.cquiroz" %%% "locales-minimal-en_us-db" % "1.5.4" ), ) @@ -182,11 +182,11 @@ lazy val tests = crossProject(JVMPlatform, JSPlatform, NativePlatform) "org.scalameta" % "junit-interface" % "1.1.1", "org.typelevel" %%% "scalacheck-effect-munit" % "2.0.0-M2", "org.typelevel" %%% "munit-cats-effect" % "2.1.0", - "org.typelevel" %%% "cats-free" % "2.11.0", - "org.typelevel" %%% "cats-laws" % "2.11.0", - "org.typelevel" %%% "cats-effect-testkit" % "3.6.3", - "org.typelevel" %%% "discipline-munit" % "2.0.0-M3", - "org.typelevel" %%% "cats-time" % "0.5.1", + "org.typelevel" %%% "cats-free" % "2.13.0", + "org.typelevel" %%% "cats-laws" % "2.13.0", + "org.typelevel" %%% "cats-effect-testkit" % "3.7.0-RC1", + "org.typelevel" %%% "discipline-munit" % "2.0.0", + "org.typelevel" %%% "cats-time" % "0.6.0", "eu.timepit" %%% "refined-cats" % refinedVersion, "org.typelevel" %%% "otel4s-sdk-trace" % otel4sVersion, "org.typelevel" %%% "otel4s-sdk-exporter-trace" % otel4sVersion, diff --git a/modules/core/native/src/main/scala/net/message/PasswordMessagePlatform.scala b/modules/core/native/src/main/scala/net/message/PasswordMessagePlatform.scala index ad022cc04..92abf4504 100644 --- a/modules/core/native/src/main/scala/net/message/PasswordMessagePlatform.scala +++ b/modules/core/native/src/main/scala/net/message/PasswordMessagePlatform.scala @@ -32,9 +32,9 @@ private[message] trait PasswordMessagePlatform { // First round if (EVP_DigestInit_ex(ctx, `type`, null) != 1) throw new RuntimeException("EVP_DigestInit_ex") - if (EVP_DigestUpdate(ctx, password.getBytes.atUnsafe(0), password.length.toULong) != 1) + if (EVP_DigestUpdate(ctx, password.getBytes.atUnsafe(0), password.length.toCSize) != 1) throw new RuntimeException("EVP_DigestUpdate") - if (EVP_DigestUpdate(ctx, user.getBytes.atUnsafe(0), user.length.toULong) != 1) + if (EVP_DigestUpdate(ctx, user.getBytes.atUnsafe(0), user.length.toCSize) != 1) throw new RuntimeException("EVP_DigestUpdate") if (EVP_DigestFinal_ex(ctx, md.atUnsafe(0), size) != 1) throw new RuntimeException("EVP_DigestFinal_ex") @@ -48,9 +48,9 @@ private[message] trait PasswordMessagePlatform { // Second round if (EVP_DigestInit_ex(ctx, `type`, null) != 1) throw new RuntimeException("EVP_DigestInit_ex") - if (EVP_DigestUpdate(ctx, hex.getBytes.atUnsafe(0), 32.toULong) != 1) + if (EVP_DigestUpdate(ctx, hex.getBytes.atUnsafe(0), 32.toCSize) != 1) throw new RuntimeException("EVP_DigestUpdate") - if (EVP_DigestUpdate(ctx, salt.atUnsafe(0), salt.length.toULong) != 1) + if (EVP_DigestUpdate(ctx, salt.atUnsafe(0), salt.length.toCSize) != 1) throw new RuntimeException("EVP_DigestUpdate") if (EVP_DigestFinal_ex(ctx, md.atUnsafe(0), size) != 1) throw new RuntimeException("EVP_DigestFinal_ex") diff --git a/modules/core/native/src/main/scala/net/message/ScramPlatform.scala b/modules/core/native/src/main/scala/net/message/ScramPlatform.scala index d5d37965f..c6f0d29ca 100644 --- a/modules/core/native/src/main/scala/net/message/ScramPlatform.scala +++ b/modules/core/native/src/main/scala/net/message/ScramPlatform.scala @@ -27,7 +27,7 @@ private[message] trait ScramPlatform { this: Scram.type => throw new RuntimeException("EVP_get_digestbyname") val md = new Array[Byte](EVP_MAX_MD_SIZE) val mdLen = stackalloc[CUnsignedInt]() - if (openssl.HMAC(evpMd, key.toArrayUnsafe.atUnsafe(0), key.size.toInt, str.toArrayUnsafe.atUnsafe(0), str.size.toULong, md.atUnsafe(0), mdLen) == null) + if (openssl.HMAC(evpMd, key.toArrayUnsafe.atUnsafe(0), key.size.toInt, str.toArrayUnsafe.atUnsafe(0), str.size.toCSize, md.atUnsafe(0), mdLen) == null) throw new RuntimeException("HMAC") ByteVector.view(md, 0, (!mdLen).toInt) } @@ -38,7 +38,7 @@ private[message] trait ScramPlatform { this: Scram.type => val `type` = EVP_get_digestbyname(c"SHA256") if (`type` == null) throw new RuntimeException("EVP_get_digestbyname") - if (EVP_Digest(input.toArrayUnsafe.atUnsafe(0), input.size.toULong, md.atUnsafe(0), size, `type`, null) != 1) + if (EVP_Digest(input.toArrayUnsafe.atUnsafe(0), input.size.toCSize, md.atUnsafe(0), size, `type`, null) != 1) throw new RuntimeException("EVP_Digest") ByteVector.view(md, 0, (!size).toInt) } diff --git a/modules/docs/src/main/scala/mdocext/Main.scala b/modules/docs/src/main/scala/mdocext/Main.scala index e75eb214d..e916a56f0 100644 --- a/modules/docs/src/main/scala/mdocext/Main.scala +++ b/modules/docs/src/main/scala/mdocext/Main.scala @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2021 by Rob Norris +// Copyright (c) 2018-2024 by Rob Norris and Contributors // This software is licensed under the MIT License (MIT). // For more information see LICENSE or https://opensource.org/licenses/MIT diff --git a/modules/docs/src/main/scala/mdocext/package.scala b/modules/docs/src/main/scala/mdocext/package.scala index 95d5f240f..a8ef7dda0 100644 --- a/modules/docs/src/main/scala/mdocext/package.scala +++ b/modules/docs/src/main/scala/mdocext/package.scala @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2021 by Rob Norris +// Copyright (c) 2018-2024 by Rob Norris and Contributors // This software is licensed under the MIT License (MIT). // For more information see LICENSE or https://opensource.org/licenses/MIT diff --git a/project/plugins.sbt b/project/plugins.sbt index 53388b2a2..df6a36399 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -8,6 +8,6 @@ addSbtPlugin("org.typelevel" % "sbt-typelevel-site" addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.7") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.1") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.20.1") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.8") addSbtPlugin("com.armanbilge" % "sbt-scala-native-config-brew-github-actions" % "0.4.0") From d73862b47dcc50291d08ac6cc45cbe9ca62f881a Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Thu, 18 Sep 2025 14:04:41 -0400 Subject: [PATCH 26/31] Bump cats-parse --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f703fa4fa..d05335a36 100644 --- a/build.sbt +++ b/build.sbt @@ -168,7 +168,7 @@ lazy val postgis = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( name := "skunk-postgis", libraryDependencies ++= Seq( - "org.typelevel" %%% "cats-parse" % "1.0.0" + "org.typelevel" %%% "cats-parse" % "1.1.0" ), ) From f355ffd711484f2da58697cbfcebe2dd634b7d30 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Thu, 18 Sep 2025 14:19:29 -0400 Subject: [PATCH 27/31] Bump circe-core --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index d05335a36..9690e5ec5 100644 --- a/build.sbt +++ b/build.sbt @@ -154,8 +154,8 @@ lazy val circe = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( name := "skunk-circe", libraryDependencies ++= Seq( - "io.circe" %%% "circe-core" % "0.14.8", - "io.circe" %%% "circe-jawn" % "0.14.8" + "io.circe" %%% "circe-core" % "0.14.14", + "io.circe" %%% "circe-jawn" % "0.14.14" ) ) From 0bcc75dcbed64bce86029a6f0ec75d0d5a125c22 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Thu, 18 Sep 2025 15:00:14 -0400 Subject: [PATCH 28/31] Fix artifact resolution --- build.sbt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 9690e5ec5..ce9d01d49 100644 --- a/build.sbt +++ b/build.sbt @@ -60,6 +60,9 @@ ThisBuild / tlFatalWarnings := false ThisBuild / resolvers += Resolver.sonatypeCentralSnapshots +ThisBuild / libraryDependencySchemes += + "org.scala-native" %% "test-interface_native0.5" % VersionScheme.Always + // This is used in a couple places lazy val fs2Version = "3.13.0-M7" lazy val openTelemetryVersion = "1.52.0" @@ -180,10 +183,8 @@ lazy val tests = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings(commonSettings) .settings( libraryDependencies ++= Seq( - "org.scalameta" %%% "munit" % "1.0.0", - "org.scalameta" % "junit-interface" % "1.1.1", - "org.typelevel" %%% "scalacheck-effect-munit" % "2.0.0-M2", - "org.typelevel" %%% "munit-cats-effect" % "2.1.0", + "org.typelevel" %%% "scalacheck-effect-munit" % "2.1.0-RC1", + "org.typelevel" %%% "munit-cats-effect" % "2.2.0-RC1", "org.typelevel" %%% "cats-free" % "2.13.0", "org.typelevel" %%% "cats-laws" % "2.13.0", "org.typelevel" %%% "cats-effect-testkit" % "3.7.0-RC1", From 718a636128594cdeda960c62d97e216810d82437 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Thu, 18 Sep 2025 15:25:56 -0400 Subject: [PATCH 29/31] Update workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e012fe017..e86d5fc12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -266,7 +266,7 @@ jobs: - name: Submit Dependencies uses: scalacenter/sbt-dependency-submission@v2 with: - modules-ignore: skunk_2.13 skunk_3 docs_2.13 docs_3 skunk_2.13 skunk_3 skunk_2.13 skunk_3 tests_sjs1_2.13 tests_sjs1_3 tests_2.13 tests_3 example_2.13 example_3 tests_native0.4_2.13 tests_native0.4_3 bench_2.13 bench_3 + modules-ignore: skunk_2.13 skunk_3 docs_2.13 docs_3 skunk_2.13 skunk_3 skunk_2.13 skunk_3 tests_sjs1_2.13 tests_sjs1_3 tests_2.13 tests_3 example_2.13 example_3 tests_native0.5_2.13 tests_native0.5_3 bench_2.13 bench_3 configs-ignore: test scala-tool scala-doc-tool test-internal site: From 2c2db116478191205a042a51a1589a324cc5dde3 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 20 Sep 2025 08:32:20 -0400 Subject: [PATCH 30/31] Disable tracing on native to avoid NPE --- .../src/main/scala/ffstest/FFrameworkPlatform.scala | 6 +++++- .../src/main/scala/ffstest/FFrameworkPlatform.scala | 6 +++++- .../src/main/scala/ffstest/FFrameworkPlatform.scala | 6 +++++- .../shared/src/main/scala/ffstest/FFramework.scala | 11 +++++++---- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/modules/tests/js/src/main/scala/ffstest/FFrameworkPlatform.scala b/modules/tests/js/src/main/scala/ffstest/FFrameworkPlatform.scala index 8c01cf4ab..e2d1376b3 100644 --- a/modules/tests/js/src/main/scala/ffstest/FFrameworkPlatform.scala +++ b/modules/tests/js/src/main/scala/ffstest/FFrameworkPlatform.scala @@ -11,4 +11,8 @@ import munit.CatsEffectSuite trait FTestPlatform extends CatsEffectSuite { implicit val fs2Compression: Compression[IO] = fs2.io.compression.fs2ioCompressionForIO -} \ No newline at end of file + + final val isJVM = false + final val isJS = true + final val isNative = false +} diff --git a/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala b/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala index 80c007b34..66ef20b58 100644 --- a/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala +++ b/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala @@ -6,4 +6,8 @@ package ffstest import munit.CatsEffectSuite -trait FTestPlatform extends CatsEffectSuite +trait FTestPlatform extends CatsEffectSuite { + final val isJVM = true + final val isJS = false + final val isNative = false +} diff --git a/modules/tests/native/src/main/scala/ffstest/FFrameworkPlatform.scala b/modules/tests/native/src/main/scala/ffstest/FFrameworkPlatform.scala index 80c007b34..d1955cc61 100644 --- a/modules/tests/native/src/main/scala/ffstest/FFrameworkPlatform.scala +++ b/modules/tests/native/src/main/scala/ffstest/FFrameworkPlatform.scala @@ -6,4 +6,8 @@ package ffstest import munit.CatsEffectSuite -trait FTestPlatform extends CatsEffectSuite +trait FTestPlatform extends CatsEffectSuite { + final val isJVM = false + final val isJS = false + final val isNative = true +} diff --git a/modules/tests/shared/src/main/scala/ffstest/FFramework.scala b/modules/tests/shared/src/main/scala/ffstest/FFramework.scala index 66db29109..ee87bfb9f 100644 --- a/modules/tests/shared/src/main/scala/ffstest/FFramework.scala +++ b/modules/tests/shared/src/main/scala/ffstest/FFramework.scala @@ -18,10 +18,13 @@ import org.typelevel.otel4s.trace.Tracer trait FTest extends CatsEffectSuite with FTestPlatform { private def withinSpan[A](name: String)(body: Tracer[IO] => IO[A]): IO[A] = - SdkTraces - .autoConfigured[IO](_.addExporterConfigurer(OtlpSpanExporterAutoConfigure[IO])) - .evalMap(_.tracerProvider.get(getClass.getName())) - .use(tracer => tracer.span(spanNameForTest(name)).surround(body(tracer))) + if (isNative) + body(Tracer.Implicits.noop) // FIXME: With auto-configured traces, PoolTest fails on Native + else + SdkTraces + .autoConfigured[IO](_.addExporterConfigurer(OtlpSpanExporterAutoConfigure[IO])) + .evalMap(_.tracerProvider.get(getClass.getName())) + .use(tracer => tracer.span(spanNameForTest(name)).surround(body(tracer))) private def spanNameForTest(name: String): String = s"${getClass.getSimpleName} - $name" From b6e0544350732550dea01f5eb0f319057ea63e02 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 20 Sep 2025 11:12:56 -0400 Subject: [PATCH 31/31] Use munit.internal.PlatformCompat --- .../js/src/main/scala/ffstest/FFrameworkPlatform.scala | 4 ---- .../jvm/src/main/scala/ffstest/FFrameworkPlatform.scala | 6 +----- .../native/src/main/scala/ffstest/FFrameworkPlatform.scala | 6 +----- .../tests/shared/src/main/scala/ffstest/FFramework.scala | 3 ++- 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/modules/tests/js/src/main/scala/ffstest/FFrameworkPlatform.scala b/modules/tests/js/src/main/scala/ffstest/FFrameworkPlatform.scala index e2d1376b3..73b00f3f1 100644 --- a/modules/tests/js/src/main/scala/ffstest/FFrameworkPlatform.scala +++ b/modules/tests/js/src/main/scala/ffstest/FFrameworkPlatform.scala @@ -11,8 +11,4 @@ import munit.CatsEffectSuite trait FTestPlatform extends CatsEffectSuite { implicit val fs2Compression: Compression[IO] = fs2.io.compression.fs2ioCompressionForIO - - final val isJVM = false - final val isJS = true - final val isNative = false } diff --git a/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala b/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala index 66ef20b58..80c007b34 100644 --- a/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala +++ b/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala @@ -6,8 +6,4 @@ package ffstest import munit.CatsEffectSuite -trait FTestPlatform extends CatsEffectSuite { - final val isJVM = true - final val isJS = false - final val isNative = false -} +trait FTestPlatform extends CatsEffectSuite diff --git a/modules/tests/native/src/main/scala/ffstest/FFrameworkPlatform.scala b/modules/tests/native/src/main/scala/ffstest/FFrameworkPlatform.scala index d1955cc61..80c007b34 100644 --- a/modules/tests/native/src/main/scala/ffstest/FFrameworkPlatform.scala +++ b/modules/tests/native/src/main/scala/ffstest/FFrameworkPlatform.scala @@ -6,8 +6,4 @@ package ffstest import munit.CatsEffectSuite -trait FTestPlatform extends CatsEffectSuite { - final val isJVM = false - final val isJS = false - final val isNative = true -} +trait FTestPlatform extends CatsEffectSuite diff --git a/modules/tests/shared/src/main/scala/ffstest/FFramework.scala b/modules/tests/shared/src/main/scala/ffstest/FFramework.scala index ee87bfb9f..f0e4d80c1 100644 --- a/modules/tests/shared/src/main/scala/ffstest/FFramework.scala +++ b/modules/tests/shared/src/main/scala/ffstest/FFramework.scala @@ -9,6 +9,7 @@ import cats.effect._ import cats.syntax.all._ import scala.reflect.ClassTag import munit.{CatsEffectSuite, Location, TestOptions} +import munit.internal.PlatformCompat import org.typelevel.otel4s.sdk.exporter.otlp.trace.autoconfigure.OtlpSpanExporterAutoConfigure import skunk.exception._ import org.typelevel.twiddles._ @@ -18,7 +19,7 @@ import org.typelevel.otel4s.trace.Tracer trait FTest extends CatsEffectSuite with FTestPlatform { private def withinSpan[A](name: String)(body: Tracer[IO] => IO[A]): IO[A] = - if (isNative) + if (PlatformCompat.isNative) body(Tracer.Implicits.noop) // FIXME: With auto-configured traces, PoolTest fails on Native else SdkTraces