diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e83242e3..30d7c116 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,10 +27,10 @@ jobs: name: Build and Test strategy: matrix: - os: [ubuntu-latest] - scala: [2.12.16, 2.13.8, 3.1.3] + os: [ubuntu-22.04] + scala: [2.12.17, 2.13.10, 3.2.1] java: [temurin@11] - project: [rootJS, rootJVM] + project: [rootJS, rootJVM, rootNative] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) @@ -66,6 +66,10 @@ jobs: ~/Library/Caches/Coursier/v1 key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} + - name: Install brew formulae (ubuntu) + if: (matrix.project == 'rootNative') && startsWith(matrix.os, 'ubuntu') + run: /home/linuxbrew/.linuxbrew/bin/brew install s2n utf8proc + - name: Set up cert permissions run: | chmod 600 world/server.key @@ -88,6 +92,10 @@ jobs: if: matrix.project == 'rootJS' run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' Test/scalaJSLinkerResult + - name: nativeLink + if: matrix.project == 'rootNative' + run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' Test/nativeLink + - name: Test run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' test @@ -99,17 +107,17 @@ jobs: if: matrix.java == 'temurin@11' run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' doc - - name: Check Doc Site (2.13.8 JVM only) - if: matrix.scala == '2.13.8' && matrix.project == 'rootJVM' + - name: Check Doc Site (2.13.10 JVM only) + if: matrix.scala == '2.13.10' && matrix.project == 'rootJVM' run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' docs/makeSite - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p modules/circe/.jvm/target target .js/target modules/docs/target modules/core/js/target modules/circe/.js/target modules/core/jvm/target modules/tests/js/target .jvm/target .native/target modules/refined/.js/target modules/refined/.jvm/target modules/tests/jvm/target modules/example/target project/target + run: mkdir -p modules/circe/.jvm/target target .js/target modules/core/native/target modules/docs/target modules/core/js/target modules/circe/.js/target modules/core/jvm/target modules/tests/js/target modules/refined/.native/target .jvm/target .native/target modules/refined/.js/target modules/refined/.jvm/target modules/circe/.native/target modules/tests/jvm/target modules/example/target modules/tests/native/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar modules/circe/.jvm/target target .js/target modules/docs/target modules/core/js/target modules/circe/.js/target modules/core/jvm/target modules/tests/js/target .jvm/target .native/target modules/refined/.js/target modules/refined/.jvm/target modules/tests/jvm/target modules/example/target project/target + run: tar cf targets.tar modules/circe/.jvm/target target .js/target modules/core/native/target modules/docs/target modules/core/js/target modules/circe/.js/target modules/core/jvm/target modules/tests/js/target modules/refined/.native/target .jvm/target .native/target modules/refined/.js/target modules/refined/.jvm/target modules/circe/.native/target modules/tests/jvm/target modules/example/target modules/tests/native/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') @@ -124,8 +132,8 @@ jobs: if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') strategy: matrix: - os: [ubuntu-latest] - scala: [2.13.8] + os: [ubuntu-22.04] + scala: [2.13.10] java: [temurin@11] runs-on: ${{ matrix.os }} steps: @@ -162,62 +170,92 @@ jobs: ~/Library/Caches/Coursier/v1 key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} - - name: Download target directories (2.12.16, rootJS) + - name: Download target directories (2.12.17, rootJS) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.17-rootJS + + - name: Inflate target directories (2.12.17, rootJS) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (2.12.17, rootJVM) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.17-rootJVM + + - name: Inflate target directories (2.12.17, rootJVM) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (2.12.17, rootNative) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.17-rootNative + + - name: Inflate target directories (2.12.17, rootNative) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (2.13.10, rootJS) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.16-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.10-rootJS - - name: Inflate target directories (2.12.16, rootJS) + - name: Inflate target directories (2.13.10, rootJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.12.16, rootJVM) + - name: Download target directories (2.13.10, rootJVM) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.16-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.10-rootJVM - - name: Inflate target directories (2.12.16, rootJVM) + - name: Inflate target directories (2.13.10, rootJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.8, rootJS) + - name: Download target directories (2.13.10, rootNative) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.10-rootNative - - name: Inflate target directories (2.13.8, rootJS) + - name: Inflate target directories (2.13.10, rootNative) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.8, rootJVM) + - name: Download target directories (3.2.1, rootJS) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.1-rootJS - - name: Inflate target directories (2.13.8, rootJVM) + - name: Inflate target directories (3.2.1, rootJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3.1.3, rootJS) + - name: Download target directories (3.2.1, rootJVM) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.3-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.1-rootJVM - - name: Inflate target directories (3.1.3, rootJS) + - name: Inflate target directories (3.2.1, rootJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3.1.3, rootJVM) + - name: Download target directories (3.2.1, rootNative) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.3-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.1-rootNative - - name: Inflate target directories (3.1.3, rootJVM) + - name: Inflate target directories (3.2.1, rootNative) run: | tar xf targets.tar rm targets.tar @@ -237,11 +275,11 @@ jobs: run: sbt '++${{ matrix.scala }}' tlRelease coverage: - name: Generate coverage report (2.13.8 JVM only) + name: Generate coverage report (2.13.10 JVM only) strategy: matrix: os: [ubuntu-latest] - scala: [2.13.8] + scala: [2.13.10] java: [temurin@11] runs-on: ${{ matrix.os }} steps: diff --git a/.scalafmt.conf b/.scalafmt.conf index 5fbe01d2..2ee8f302 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,3 +1,3 @@ -version = "3.5.9" +version = "3.6.1" runner.dialect = Scala213Source3 project.includePaths = [] # disables formatting diff --git a/build.sbt b/build.sbt index 2fd43303..8c0d22cb 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ ThisBuild / tlBaseVersion := "0.4" // Our Scala versions. -lazy val `scala-2.12` = "2.12.16" -lazy val `scala-2.13` = "2.13.8" -lazy val `scala-3.0` = "3.1.3" +lazy val `scala-2.12` = "2.12.17" +lazy val `scala-2.13` = "2.13.10" +lazy val `scala-3.0` = "3.2.1" ThisBuild / scalaVersion := `scala-2.13` ThisBuild / crossScalaVersions := @@ -17,9 +17,13 @@ ThisBuild / developers := List( ThisBuild / tlCiReleaseBranches := Seq("main") // publish snapshits on `main` ThisBuild / tlSonatypeUseLegacyHost := false +ThisBuild / githubWorkflowOSes := Seq("ubuntu-22.04") ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11")) ThisBuild / tlJdkRelease := Some(8) +ThisBuild / githubWorkflowBuildPreamble ++= nativeBrewInstallWorkflowSteps.value +ThisBuild / nativeBrewInstallCond := Some("matrix.project == 'rootNative'") + lazy val setupCertAndDocker = Seq( WorkflowStep.Run( commands = List("chmod 600 world/server.key", "sudo chown 999 world/server.key"), @@ -69,8 +73,8 @@ ThisBuild / libraryDependencySchemes ++= Seq( ) // This is used in a couple places -lazy val fs2Version = "3.2.14" -lazy val natchezVersion = "0.1.6" +lazy val fs2Version = "3.4.0" +lazy val natchezVersion = "0.2.2" // Global Settings lazy val commonSettings = Seq( @@ -97,9 +101,6 @@ lazy val commonSettings = Seq( "-sourcepath", (LocalRootProject / baseDirectory).value.getAbsolutePath, "-doc-source-url", "https://github.com/tpolecat/skunk/blob/v" + version.value + "€{FILE_PATH}.scala", ), - libraryDependencies ++= Seq( - compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.2" cross CrossVersion.full), - ).filterNot(_ => tlIsScala3.value), // Coverage Exclusions coverageExcludedPackages := "ffstest.*;tests.*;example.*;natchez.http4s.*", @@ -107,27 +108,6 @@ lazy val commonSettings = Seq( // uncomment in case of emergency // scalacOptions ++= { if (scalaVersion.value.startsWith("3.")) Seq("-source:3.0-migration") else Nil }, - // Add some more source directories - Compile / unmanagedSourceDirectories ++= { - val sourceDir = (Compile / sourceDirectory).value - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((3, _)) => Seq(sourceDir / "scala-2.13+", file(sourceDir.getPath.replaceFirst("jvm", "shared").replaceFirst("js", "shared")) / "scala-2.13+") - case Some((2, 12)) => Seq() - case Some((2, _)) => Seq(sourceDir / "scala-2.13+", file(sourceDir.getPath.replaceFirst("jvm", "shared").replaceFirst("js", "shared")) / "scala-2.13+") - case _ => Seq() - } - }, - - - // dottydoc really doesn't work at all right now - Compile / doc / sources := { - val old = (Compile / doc / sources).value - if (scalaVersion.value.startsWith("3.")) - Seq() - else - old - }, - ) lazy val skunk = tlCrossRootProject @@ -135,7 +115,7 @@ lazy val skunk = tlCrossRootProject .aggregate(core, tests, circe, refined, example) .settings(commonSettings) -lazy val core = crossProject(JVMPlatform, JSPlatform) +lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Full) .in(file("modules/core")) .enablePlugins(AutomateHeaderPlugin) @@ -145,30 +125,34 @@ lazy val core = crossProject(JVMPlatform, JSPlatform) description := "Tagless, non-blocking data access library for Postgres.", scalacOptions ~= (_.filterNot(_ == "-source:3.0-migration")), libraryDependencies ++= Seq( - "org.typelevel" %%% "cats-core" % "2.8.0", - "org.typelevel" %%% "cats-effect" % "3.3.14", + "org.typelevel" %%% "cats-core" % "2.9.0", + "org.typelevel" %%% "cats-effect" % "3.4.1", "co.fs2" %%% "fs2-core" % fs2Version, "co.fs2" %%% "fs2-io" % fs2Version, "org.scodec" %%% "scodec-bits" % "1.1.34", "org.scodec" %%% "scodec-core" % (if (tlIsScala3.value) "2.2.0" else "1.11.10"), "org.scodec" %%% "scodec-cats" % "1.2.0", "org.tpolecat" %%% "natchez-core" % natchezVersion, - "org.tpolecat" %%% "sourcepos" % "1.0.1", + "org.tpolecat" %%% "sourcepos" % "1.1.0", "org.scala-lang.modules" %%% "scala-collection-compat" % "2.8.1", - ) ++ Seq( - "com.beachape" %%% "enumeratum" % "1.6.1", - ).filterNot(_ => tlIsScala3.value) + ) ).jvmSettings( libraryDependencies += "com.ongres.scram" % "client" % "2.1", - ).jsSettings( + ) + .platformsSettings(JVMPlatform, JSPlatform)( + libraryDependencies ++= Seq( + "com.beachape" %%% "enumeratum" % "1.6.1", + ).filterNot(_ => tlIsScala3.value) + ) + .platformsSettings(JSPlatform, NativePlatform)( libraryDependencies ++= Seq( "com.armanbilge" %%% "saslprep" % "0.1.1", "io.github.cquiroz" %%% "scala-java-time" % "2.4.0", - "io.github.cquiroz" %%% "locales-minimal-en_us-db" % "1.4.1" + "io.github.cquiroz" %%% "locales-minimal-en_us-db" % "1.5.0" ), ) -lazy val refined = crossProject(JVMPlatform, JSPlatform) +lazy val refined = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) .in(file("modules/refined")) .dependsOn(core) @@ -176,11 +160,11 @@ lazy val refined = crossProject(JVMPlatform, JSPlatform) .settings(commonSettings) .settings( libraryDependencies ++= Seq( - "eu.timepit" %%% "refined" % "0.9.29", + "eu.timepit" %%% "refined" % "0.10.1", ) ) -lazy val circe = crossProject(JVMPlatform, JSPlatform) +lazy val circe = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) .in(file("modules/circe")) .dependsOn(core) @@ -189,12 +173,12 @@ lazy val circe = crossProject(JVMPlatform, JSPlatform) .settings( name := "skunk-circe", libraryDependencies ++= Seq( - "io.circe" %%% "circe-core" % "0.14.2", - "io.circe" %%% "circe-parser" % "0.14.2" + "io.circe" %%% "circe-core" % "0.14.3", + "io.circe" %%% "circe-parser" % "0.14.3" ) ) -lazy val tests = crossProject(JVMPlatform, JSPlatform) +lazy val tests = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Full) .in(file("modules/tests")) .dependsOn(core, circe) @@ -203,21 +187,26 @@ lazy val tests = crossProject(JVMPlatform, JSPlatform) .settings( scalacOptions -= "-Xfatal-warnings", libraryDependencies ++= Seq( - "org.scalameta" %%% "munit" % "0.7.29", - "org.scalameta" % "junit-interface" % "0.7.29", - "org.typelevel" %%% "scalacheck-effect-munit" % "1.0.4", - "org.typelevel" %%% "munit-cats-effect-3" % "1.0.7", - "org.typelevel" %%% "cats-free" % "2.8.0", - "org.typelevel" %%% "cats-laws" % "2.8.0", - "org.typelevel" %%% "discipline-munit" % "1.0.9", - ) ++ Seq( - "io.chrisdavenport" %%% "cats-time" % "0.3.4", - ).filterNot(_ => scalaVersion.value.startsWith("3.")), + "org.scalameta" %%% "munit" % "1.0.0-M6", + "org.scalameta" % "junit-interface" % "1.0.0-M6", + "org.typelevel" %%% "scalacheck-effect-munit" % "2.0.0-M2", + "org.typelevel" %%% "munit-cats-effect" % "2.0.0-M3", + "org.typelevel" %%% "cats-free" % "2.9.0", + "org.typelevel" %%% "cats-laws" % "2.9.0", + "org.typelevel" %%% "discipline-munit" % "2.0.0-M3", + "org.typelevel" %%% "cats-time" % "0.5.1", + ), testFrameworks += new TestFramework("munit.Framework") ) .jsSettings( Test / scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)), ) + .nativeEnablePlugins(ScalaNativeBrewedConfigPlugin) + .nativeSettings( + libraryDependencies += "com.armanbilge" %%% "epollcat" % "0.0-ab1026e", + Test / nativeBrewFormulas ++= Set("s2n", "utf8proc"), + Test / envVars ++= Map("S2N_DONT_MLOCK" -> "1") + ) lazy val example = project .in(file("modules/example")) diff --git a/modules/core/shared/src/main/scala-2/SqlState.scala b/modules/core/js-jvm/src/main/scala-2/SqlState.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/SqlState.scala rename to modules/core/js-jvm/src/main/scala-2/SqlState.scala diff --git a/modules/core/shared/src/main/scala-2/codec/EnumCodecPlatform.scala b/modules/core/js-jvm/src/main/scala-2/codec/EnumCodecPlatform.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/codec/EnumCodecPlatform.scala rename to modules/core/js-jvm/src/main/scala-2/codec/EnumCodecPlatform.scala diff --git a/modules/core/js/src/main/scala/SSLPlatform.scala b/modules/core/js-native/src/main/scala/SSLPlatform.scala similarity index 100% rename from modules/core/js/src/main/scala/SSLPlatform.scala rename to modules/core/js-native/src/main/scala/SSLPlatform.scala diff --git a/modules/core/js/src/main/scala/net/message/Scram.scala b/modules/core/js-native/src/main/scala/message/Scram.scala similarity index 76% rename from modules/core/js/src/main/scala/net/message/Scram.scala rename to modules/core/js-native/src/main/scala/message/Scram.scala index e01678d2..68ac6c03 100644 --- a/modules/core/js/src/main/scala/net/message/Scram.scala +++ b/modules/core/js-native/src/main/scala/message/Scram.scala @@ -8,16 +8,13 @@ import com.armanbilge.SaslPrep import scodec.bits.ByteVector import scodec.codecs.utf8 -import scala.scalajs.js -import scala.scalajs.js.typedarray.Uint8Array - /** * Partial implementation of [RFC5802](https://tools.ietf.org/html/rfc5802), as needed by PostgreSQL. * * That is, only features used by PostgreSQL are implemented -- e.g., channel binding is not supported and * optional message fields omitted by PostgreSQL are not supported. */ -private[skunk] object Scram { +private[skunk] object Scram extends ScramPlatform { val SaslMechanism = "SCRAM-SHA-256" val NoChannelBinding = ByteVector.view("n,,".getBytes) @@ -26,11 +23,6 @@ private[skunk] object Scram { def bytesUtf8: ByteVector = ByteVector.view(value.getBytes(java.nio.charset.StandardCharsets.UTF_8)) } - def clientFirstBareWithRandomNonce: ByteVector = { - val nonce = ByteVector.view(crypto.randomBytes(32).asInstanceOf[Uint8Array]).toBase64 - clientFirstBareWithNonce(nonce) - } - def clientFirstBareWithNonce(nonce: String): ByteVector = s"n=,r=${nonce}".bytesUtf8 @@ -69,27 +61,6 @@ private[skunk] object Scram { } } - private val crypto = js.Dynamic.global.require("crypto") - - private def HMAC(key: ByteVector, str: ByteVector): ByteVector = { - val mac = crypto.createHmac("sha256", key.toUint8Array) - mac.update(str.toUint8Array) - ByteVector.view(mac.digest().asInstanceOf[Uint8Array]) - } - - private def H(input: ByteVector): ByteVector = { - val hash = crypto.createHash("sha256") - hash.update(input.toUint8Array) - ByteVector.view(hash.digest().asInstanceOf[Uint8Array]) - } - - private def Hi(str: String, salt: ByteVector, iterations: Int): ByteVector = { - // TODO It is unfortunate that we have to use a sync API here when an async is available - // To make the change here will require running an F[_]: Async up the hiearchy - val salted = crypto.pbkdf2Sync(str, salt.toUint8Array, iterations, 8 * 32, "sha256") - ByteVector.view(salted.asInstanceOf[Uint8Array]).take(32) - } - private def makeClientProofAndServerSignature(password: String, salt: ByteVector, iterations: Int, clientFirstMessageBare: ByteVector, serverFirstMessage: ByteVector, clientFinalMessageWithoutProof: ByteVector): (ClientProof, Verifier) = { val saltedPassword = Hi(SaslPrep.saslPrepStored(password), salt, iterations) val clientKey = HMAC(saltedPassword, "Client Key".bytesUtf8) diff --git a/modules/core/js/src/main/scala/net/protocol/StartupPlatform.scala b/modules/core/js-native/src/main/scala/protocol/StartupPlatform.scala similarity index 100% rename from modules/core/js/src/main/scala/net/protocol/StartupPlatform.scala rename to modules/core/js-native/src/main/scala/protocol/StartupPlatform.scala diff --git a/modules/core/js/src/main/scala/net/message/ScramPlatform.scala b/modules/core/js/src/main/scala/net/message/ScramPlatform.scala new file mode 100644 index 00000000..82c35517 --- /dev/null +++ b/modules/core/js/src/main/scala/net/message/ScramPlatform.scala @@ -0,0 +1,40 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package skunk.net.message + +import scodec.bits.ByteVector + +import scala.scalajs.js +import scala.scalajs.js.typedarray.Uint8Array + +private[message] trait ScramPlatform { this: Scram.type => + + private val crypto = js.Dynamic.global.require("crypto") + + def clientFirstBareWithRandomNonce: ByteVector = { + val nonce = ByteVector.view(crypto.randomBytes(32).asInstanceOf[Uint8Array]).toBase64 + clientFirstBareWithNonce(nonce) + } + + private[message] def HMAC(key: ByteVector, str: ByteVector): ByteVector = { + val mac = crypto.createHmac("sha256", key.toUint8Array) + mac.update(str.toUint8Array) + ByteVector.view(mac.digest().asInstanceOf[Uint8Array]) + } + + private[message] def H(input: ByteVector): ByteVector = { + val hash = crypto.createHash("sha256") + hash.update(input.toUint8Array) + ByteVector.view(hash.digest().asInstanceOf[Uint8Array]) + } + + private[message] def Hi(str: String, salt: ByteVector, iterations: Int): ByteVector = { + // TODO It is unfortunate that we have to use a sync API here when an async is available + // To make the change here will require running an F[_]: Async up the hiearchy + val salted = crypto.pbkdf2Sync(str, salt.toUint8Array, iterations, 8 * 32, "sha256") + ByteVector.view(salted.asInstanceOf[Uint8Array]).take(32) + } + +} diff --git a/modules/core/jvm/src/main/scala/SSLPlatform.scala b/modules/core/jvm/src/main/scala/SSLPlatform.scala index efc77efa..6497dcfe 100644 --- a/modules/core/jvm/src/main/scala/SSLPlatform.scala +++ b/modules/core/jvm/src/main/scala/SSLPlatform.scala @@ -5,7 +5,7 @@ package skunk import cats._ -import cats.syntax.all._ +import cats.effect.Resource import java.nio.file.Path import java.security.KeyStore import javax.net.ssl.SSLContext @@ -17,8 +17,8 @@ private[skunk] trait SSLCompanionPlatform { this: SSL.type => /** Creates a `SSL` from an `SSLContext`. */ def fromSSLContext(ctx: SSLContext): SSL = new SSL { - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] = - Network[F].tlsContext.fromSSLContext(ctx).pure[F] + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] = + Resource.pure(Network[F].tlsContext.fromSSLContext(ctx)) } /** Creates a `SSL` from the specified key store file. */ @@ -28,8 +28,8 @@ private[skunk] trait SSLCompanionPlatform { this: SSL.type => keyPassword: Array[Char], ): SSL = new SSL { - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] = - Network[F].tlsContext.fromKeyStoreFile(file, storePassword, keyPassword) + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] = + Resource.eval(Network[F].tlsContext.fromKeyStoreFile(file, storePassword, keyPassword)) } /** Creates a `SSL` from the specified class path resource. */ @@ -39,8 +39,8 @@ private[skunk] trait SSLCompanionPlatform { this: SSL.type => keyPassword: Array[Char], ): SSL = new SSL { - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] = - Network[F].tlsContext.fromKeyStoreResource(resource, storePassword, keyPassword) + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] = + Resource.eval(Network[F].tlsContext.fromKeyStoreResource(resource, storePassword, keyPassword)) } /** Creates a `TLSContext` from the specified key store. */ @@ -49,8 +49,8 @@ private[skunk] trait SSLCompanionPlatform { this: SSL.type => keyPassword: Array[Char], ): SSL = new SSL { - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] = - Network[F].tlsContext.fromKeyStore(keyStore, keyPassword) + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] = + Resource.eval(Network[F].tlsContext.fromKeyStore(keyStore, keyPassword)) } } diff --git a/modules/core/native/src/main/scala-2/SqlState.scala b/modules/core/native/src/main/scala-2/SqlState.scala new file mode 100644 index 00000000..15f5e7f9 --- /dev/null +++ b/modules/core/native/src/main/scala-2/SqlState.scala @@ -0,0 +1,1424 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package skunk + +import scala.collection.immutable.IndexedSeq +import skunk.exception.PostgresErrorException + +/** Enumerated type of Postgres error codes. See the companion object for more information. */ +sealed abstract class SqlState(val code: String) { + + def unapply(e: Throwable): Option[PostgresErrorException] = + Some(e).collect { case e: PostgresErrorException if e.code == code => e } + +} + +/** + * Enumerated type of Postgres error codes. These can be used as extractors for error handling, + * for example: + * {{{ + * doSomething.recoverWith { case SqlState.ForeignKeyViolation(ex) => ... } + * }}} + * @see [[https://www.postgresql.org/docs/10/errcodes-appendix.html PostgreSQL Error Codes]] + */ +object SqlState { + + /** + * SqlState `25001` + * @group Instances + */ + case object ActiveSqlTransaction extends SqlState("25001") + + /** + * SqlState `57P01` + * @group Instances + */ + case object AdminShutdown extends SqlState("57P01") + + /** + * SqlState `42P09` + * @group Instances + */ + case object AmbiguousAlias extends SqlState("42P09") + + /** + * SqlState `42702` + * @group Instances + */ + case object AmbiguousColumn extends SqlState("42702") + + /** + * SqlState `42725` + * @group Instances + */ + case object AmbiguousFunction extends SqlState("42725") + + /** + * SqlState `42P08` + * @group Instances + */ + case object AmbiguousParameter extends SqlState("42P08") + + /** + * SqlState `2202E` + * @group Instances + */ + case object ArraySubscriptError extends SqlState("2202E") + + /** + * SqlState `22P04` + * @group Instances + */ + case object BadCopyFileFormat extends SqlState("22P04") + + /** + * SqlState `25002` + * @group Instances + */ + case object BranchTransactionAlreadyActive extends SqlState("25002") + + /** + * SqlState `42846` + * @group Instances + */ + case object CannotCoerce extends SqlState("42846") + + /** + * SqlState `57P03` + * @group Instances + */ + case object CannotConnectNow extends SqlState("57P03") + + /** + * SqlState `55P02` + * @group Instances + */ + case object CantChangeRuntimeParam extends SqlState("55P02") + + /** + * SqlState `21000` + * @group Instances + */ + case object CardinalityViolation extends SqlState("21000") + + /** + * SqlState `20000` + * @group Instances + */ + case object CaseNotFound extends SqlState("20000") + + /** + * SqlState `22021` + * @group Instances + */ + case object CharacterNotInRepertoire extends SqlState("22021") + + /** + * SqlState `23514` + * @group Instances + */ + case object CheckViolation extends SqlState("23514") + + /** + * SqlState `F0000` + * @group Instances + */ + case object ConfigFileError extends SqlState("F0000") + + /** + * SqlState `08003` + * @group Instances + */ + case object ConnectionDoesNotExist extends SqlState("08003") + + /** + * SqlState `08000` + * @group Instances + */ + case object ConnectionException extends SqlState("08000") + + /** + * SqlState `08006` + * @group Instances + */ + case object ConnectionFailure extends SqlState("08006") + + /** + * SqlState `38001` + * @group Instances + */ + case object ContainingSqlNotPermitted extends SqlState("38001") + + /** + * SqlState `57P02` + * @group Instances + */ + case object CrashShutdown extends SqlState("57P02") + + /** + * SqlState `XX001` + * @group Instances + */ + case object DataCorrupted extends SqlState("XX001") + + /** + * SqlState `22000` + * @group Instances + */ + case object DataException extends SqlState("22000") + + /** + * SqlState `57P04` + * @group Instances + */ + case object DatabaseDropped extends SqlState("57P04") + + /** + * SqlState `42804` + * @group Instances + */ + case object DatatypeMismatch extends SqlState("42804") + + /** + * SqlState `22008` + * @group Instances + */ + case object DatetimeFieldOverflow extends SqlState("22008") + + /** + * SqlState `40P01` + * @group Instances + */ + case object DeadlockDetected extends SqlState("40P01") + + /** + * SqlState `2BP01` + * @group Instances + */ + case object DependentObjectsStillExist extends SqlState("2BP01") + + /** + * SqlState `2B000` + * @group Instances + */ + case object DependentPrivilegeDescriptorsStillExist extends SqlState("2B000") + + /** + * SqlState `01P01` + * @group Instances + */ + case object DeprecatedFeature extends SqlState("01P01") + + /** + * SqlState `53100` + * @group Instances + */ + case object DiskFull extends SqlState("53100") + + /** + * SqlState `22012` + * @group Instances + */ + case object DivisionByZero extends SqlState("22012") + + /** + * SqlState `42712` + * @group Instances + */ + case object DuplicateAlias extends SqlState("42712") + + /** + * SqlState `42701` + * @group Instances + */ + case object DuplicateColumn extends SqlState("42701") + + /** + * SqlState `42P03` + * @group Instances + */ + case object DuplicateCursor extends SqlState("42P03") + + /** + * SqlState `42P04` + * @group Instances + */ + case object DuplicateDatabase extends SqlState("42P04") + + /** + * SqlState `58P02` + * @group Instances + */ + case object DuplicateFile extends SqlState("58P02") + + /** + * SqlState `42723` + * @group Instances + */ + case object DuplicateFunction extends SqlState("42723") + + /** + * SqlState `42710` + * @group Instances + */ + case object DuplicateObject extends SqlState("42710") + + /** + * SqlState `42P05` + * @group Instances + */ + case object DuplicatePreparedStatement extends SqlState("42P05") + + /** + * SqlState `42P06` + * @group Instances + */ + case object DuplicateSchema extends SqlState("42P06") + + /** + * SqlState `42P07` + * @group Instances + */ + case object DuplicateTable extends SqlState("42P07") + + /** + * SqlState `0100C` + * @group Instances + */ + case object DynamicResultSetsReturned extends SqlState("0100C") + + /** + * SqlState `22005` + * @group Instances + */ + case object ErrorInAssignment extends SqlState("22005") + + /** + * SqlState `2200B` + * @group Instances + */ + case object EscapeCharacterConflict extends SqlState("2200B") + + /** + * SqlState `23P01` + * @group Instances + */ + case object ExclusionViolation extends SqlState("23P01") + + /** + * SqlState `38000` + * @group Instances + */ + case object ExternalRoutineException extends SqlState("38000") + + /** + * SqlState `39000` + * @group Instances + */ + case object ExternalRoutineInvocationException extends SqlState("39000") + + /** + * SqlState `0A000` + * @group Instances + */ + case object FeatureNotSupported extends SqlState("0A000") + + /** + * SqlState `22P01` + * @group Instances + */ + case object FloatingPointException extends SqlState("22P01") + + /** + * SqlState `23503` + * @group Instances + */ + case object ForeignKeyViolation extends SqlState("23503") + + /** + * SqlState `2F005` + * @group Instances + */ + case object FunctionExecutedNoReturnStatement extends SqlState("2F005") + + /** + * SqlState `42803` + * @group Instances + */ + case object GroupingError extends SqlState("42803") + + /** + * SqlState `25008` + * @group Instances + */ + case object HeldCursorRequiresSameIsolationLevel extends SqlState("25008") + + /** + * SqlState `01008` + * @group Instances + */ + case object ImplicitZeroBitPadding extends SqlState("01008") + + /** + * SqlState `25P02` + * @group Instances + */ + case object InFailedSqlTransaction extends SqlState("25P02") + + /** + * SqlState `25003` + * @group Instances + */ + case object InappropriateAccessModeForBranchTransaction extends SqlState("25003") + + /** + * SqlState `25004` + * @group Instances + */ + case object InappropriateIsolationLevelForBranchTransaction extends SqlState("25004") + + /** + * SqlState `42P18` + * @group Instances + */ + case object IndeterminateDatatype extends SqlState("42P18") + + /** + * SqlState `XX002` + * @group Instances + */ + case object IndexCorrupted extends SqlState("XX002") + + /** + * SqlState `22022` + * @group Instances + */ + case object IndicatorOverflow extends SqlState("22022") + + /** + * SqlState `42501` + * @group Instances + */ + case object InsufficientPrivilege extends SqlState("42501") + + /** + * SqlState `53000` + * @group Instances + */ + case object InsufficientResources extends SqlState("53000") + + /** + * SqlState `23000` + * @group Instances + */ + case object IntegrityConstraintViolation extends SqlState("23000") + + /** + * SqlState `XX000` + * @group Instances + */ + case object InternalError extends SqlState("XX000") + + /** + * SqlState `22015` + * @group Instances + */ + case object IntervalFieldOverflow extends SqlState("22015") + + /** + * SqlState `2201E` + * @group Instances + */ + case object InvalidArgumentForLogarithm extends SqlState("2201E") + + /** + * SqlState `22016` + * @group Instances + */ + case object InvalidArgumentForNthValueFunction extends SqlState("22016") + + /** + * SqlState `22014` + * @group Instances + */ + case object InvalidArgumentForNtileFunction extends SqlState("22014") + + /** + * SqlState `2201F` + * @group Instances + */ + case object InvalidArgumentForPowerFunction extends SqlState("2201F") + + /** + * SqlState `2201G` + * @group Instances + */ + case object InvalidArgumentForWidthBucketFunction extends SqlState("2201G") + + /** + * SqlState `28000` + * @group Instances + */ + case object InvalidAuthorizationSpecification extends SqlState("28000") + + /** + * SqlState `22P03` + * @group Instances + */ + case object InvalidBinaryRepresentation extends SqlState("22P03") + + /** + * SqlState `3D000` + * @group Instances + */ + case object InvalidCatalogName extends SqlState("3D000") + + /** + * SqlState `22018` + * @group Instances + */ + case object InvalidCharacterValueForCast extends SqlState("22018") + + /** + * SqlState `42611` + * @group Instances + */ + case object InvalidColumnDefinition extends SqlState("42611") + + /** + * SqlState `42P10` + * @group Instances + */ + case object InvalidColumnReference extends SqlState("42P10") + + /** + * SqlState `42P11` + * @group Instances + */ + case object InvalidCursorDefinition extends SqlState("42P11") + + /** + * SqlState `34000` + * @group Instances + */ + case object InvalidCursorName extends SqlState("34000") + + /** + * SqlState `24000` + * @group Instances + */ + case object InvalidCursorState extends SqlState("24000") + + /** + * SqlState `42P12` + * @group Instances + */ + case object InvalidDatabaseDefinition extends SqlState("42P12") + + /** + * SqlState `22007` + * @group Instances + */ + case object InvalidDatetimeFormat extends SqlState("22007") + + /** + * SqlState `22019` + * @group Instances + */ + case object InvalidEscapeCharacter extends SqlState("22019") + + /** + * SqlState `2200D` + * @group Instances + */ + case object InvalidEscapeOctet extends SqlState("2200D") + + /** + * SqlState `22025` + * @group Instances + */ + case object InvalidEscapeSequence extends SqlState("22025") + + /** + * SqlState `42830` + * @group Instances + */ + case object InvalidForeignKey extends SqlState("42830") + + /** + * SqlState `42P13` + * @group Instances + */ + case object InvalidFunctionDefinition extends SqlState("42P13") + + /** + * SqlState `0LP01` + * @group Instances + */ + case object InvalidGrantOperation extends SqlState("0LP01") + + /** + * SqlState `0L000` + * @group Instances + */ + case object InvalidGrantor extends SqlState("0L000") + + /** + * SqlState `22010` + * @group Instances + */ + case object InvalidIndicatorParameterValue extends SqlState("22010") + + /** + * SqlState `0F001` + * @group Instances + */ + case object InvalidLocatorSpecification extends SqlState("0F001") + + /** + * SqlState `42602` + * @group Instances + */ + case object InvalidName extends SqlState("42602") + + /** + * SqlState `42P17` + * @group Instances + */ + case object InvalidObjectDefinition extends SqlState("42P17") + + /** + * SqlState `22023` + * @group Instances + */ + case object InvalidParameterValue extends SqlState("22023") + + /** + * SqlState `28P01` + * @group Instances + */ + case object InvalidPassword extends SqlState("28P01") + + /** + * SqlState `42P14` + * @group Instances + */ + case object InvalidPreparedStatementDefinition extends SqlState("42P14") + + /** + * SqlState `42P19` + * @group Instances + */ + case object InvalidRecursion extends SqlState("42P19") + + /** + * SqlState `2201B` + * @group Instances + */ + case object InvalidRegularExpression extends SqlState("2201B") + + /** + * SqlState `0P000` + * @group Instances + */ + case object InvalidRoleSpecification extends SqlState("0P000") + + /** + * SqlState `2201W` + * @group Instances + */ + case object InvalidRowCountInLimitClause extends SqlState("2201W") + + /** + * SqlState `2201X` + * @group Instances + */ + case object InvalidRowCountInResultOffsetClause extends SqlState("2201X") + + /** + * SqlState `3B001` + * @group Instances + */ + case object InvalidSavepointSpecification extends SqlState("3B001") + + /** + * SqlState `42P15` + * @group Instances + */ + case object InvalidSchemaDefinition extends SqlState("42P15") + + /** + * SqlState `3F000` + * @group Instances + */ + case object InvalidSchemaName extends SqlState("3F000") + + /** + * SqlState `26000` + * @group Instances + */ + case object InvalidSqlStatementName extends SqlState("26000") + + /** + * SqlState `39001` + * @group Instances + */ + case object InvalidSqlstateReturned extends SqlState("39001") + + /** + * SqlState `42P16` + * @group Instances + */ + case object InvalidTableDefinition extends SqlState("42P16") + + /** + * SqlState `22P02` + * @group Instances + */ + case object InvalidTextRepresentation extends SqlState("22P02") + + /** + * SqlState `22009` + * @group Instances + */ + case object InvalidTimeZoneDisplacementValue extends SqlState("22009") + + /** + * SqlState `0B000` + * @group Instances + */ + case object InvalidTransactionInitiation extends SqlState("0B000") + + /** + * SqlState `25000` + * @group Instances + */ + case object InvalidTransactionState extends SqlState("25000") + + /** + * SqlState `2D000` + * @group Instances + */ + case object InvalidTransactionTermination extends SqlState("2D000") + + /** + * SqlState `2200C` + * @group Instances + */ + case object InvalidUseOfEscapeCharacter extends SqlState("2200C") + + /** + * SqlState `2200S` + * @group Instances + */ + case object InvalidXmlComment extends SqlState("2200S") + + /** + * SqlState `2200N` + * @group Instances + */ + case object InvalidXmlContent extends SqlState("2200N") + + /** + * SqlState `2200M` + * @group Instances + */ + case object InvalidXmlDocument extends SqlState("2200M") + + /** + * SqlState `2200T` + * @group Instances + */ + case object InvalidXmlProcessingInstruction extends SqlState("2200T") + + /** + * SqlState `58030` + * @group Instances + */ + case object IoError extends SqlState("58030") + + /** + * SqlState `0F000` + * @group Instances + */ + case object LocatorException extends SqlState("0F000") + + /** + * SqlState `F0001` + * @group Instances + */ + case object LockFileExists extends SqlState("F0001") + + /** + * SqlState `55P03` + * @group Instances + */ + case object LockNotAvailable extends SqlState("55P03") + + /** + * SqlState `2F002` + * @group Instances + */ + case object ModifyingSqlDataNotPermitted2F extends SqlState("2F002") + + /** + * SqlState `38002` + * @group Instances + */ + case object ModifyingSqlDataNotPermitted38 extends SqlState("38002") + + /** + * SqlState `2200G` + * @group Instances + */ + case object MostSpecificTypeMismatch extends SqlState("2200G") + + /** + * SqlState `42622` + * @group Instances + */ + case object NameTooLong extends SqlState("42622") + + /** + * SqlState `25P01` + * @group Instances + */ + case object NoActiveSqlTransaction extends SqlState("25P01") + + /** + * SqlState `25005` + * @group Instances + */ + case object NoActiveSqlTransactionForBranchTransaction extends SqlState("25005") + + /** + * SqlState `02001` + * @group Instances + */ + case object NoAdditionalDynamicResultSetsReturned extends SqlState("02001") + + /** + * SqlState `02000` + * @group Instances + */ + case object NoData extends SqlState("02000") + + /** + * SqlState `P0002` + * @group Instances + */ + case object NoDataFound extends SqlState("P0002") + + /** + * SqlState `22P06` + * @group Instances + */ + case object NonstandardUseOfEscapeCharacter extends SqlState("22P06") + + /** + * SqlState `2200L` + * @group Instances + */ + case object NotAnXmlDocument extends SqlState("2200L") + + /** + * SqlState `23502` + * @group Instances + */ + case object NotNullViolation extends SqlState("23502") + + /** + * SqlState `01003` + * @group Instances + */ + case object NullValueEliminatedInSetFunction extends SqlState("01003") + + /** + * SqlState `22002` + * @group Instances + */ + case object NullValueNoIndicatorParameter extends SqlState("22002") + + /** + * SqlState `22004` + * @group Instances + */ + case object NullValueNotAllowed extends SqlState("22004") + + /** + * SqlState `39004` + * @group Instances + */ + case object NullValueNotAllowed39 extends SqlState("39004") + + /** + * SqlState `22003` + * @group Instances + */ + case object NumericValueOutOfRange extends SqlState("22003") + + /** + * SqlState `55006` + * @group Instances + */ + case object ObjectInUse extends SqlState("55006") + + /** + * SqlState `55000` + * @group Instances + */ + case object ObjectNotInPrerequisiteState extends SqlState("55000") + + /** + * SqlState `57000` + * @group Instances + */ + case object OperatorIntervention extends SqlState("57000") + + /** + * SqlState `53200` + * @group Instances + */ + case object OutOfMemory extends SqlState("53200") + + /** + * SqlState `P0000` + * @group Instances + */ + case object PlpgsqlError extends SqlState("P0000") + + /** + * SqlState `01007` + * @group Instances + */ + case object PrivilegeNotGranted extends SqlState("01007") + + /** + * SqlState `01006` + * @group Instances + */ + case object PrivilegeNotRevoked extends SqlState("01006") + + /** + * SqlState `54000` + * @group Instances + */ + case object ProgramLimitExceeded extends SqlState("54000") + + /** + * SqlState `2F003` + * @group Instances + */ + case object ProhibitedSqlStatementAttempted2F extends SqlState("2F003") + + /** + * SqlState `38003` + * @group Instances + */ + case object ProhibitedSqlStatementAttempted38 extends SqlState("38003") + + /** + * SqlState `08P01` + * @group Instances + */ + case object ProtocolViolation extends SqlState("08P01") + + /** + * SqlState `57014` + * @group Instances + */ + case object QueryCanceled extends SqlState("57014") + + /** + * SqlState `P0001` + * @group Instances + */ + case object RaiseException extends SqlState("P0001") + + /** + * SqlState `25006` + * @group Instances + */ + case object ReadOnlySqlTransaction extends SqlState("25006") + + /** + * SqlState `2F004` + * @group Instances + */ + case object ReadingSqlDataNotPermitted2F extends SqlState("2F004") + + /** + * SqlState `38004` + * @group Instances + */ + case object ReadingSqlDataNotPermitted38 extends SqlState("38004") + + /** + * SqlState `42939` + * @group Instances + */ + case object ReservedName extends SqlState("42939") + + /** + * SqlState `23001` + * @group Instances + */ + case object RestrictViolation extends SqlState("23001") + + /** + * SqlState `3B000` + * @group Instances + */ + case object SavepointException extends SqlState("3B000") + + /** + * SqlState `25007` + * @group Instances + */ + case object SchemaAndDataStatementMixingNotSupported extends SqlState("25007") + + /** + * SqlState `40001` + * @group Instances + */ + case object SerializationFailure extends SqlState("40001") + + /** + * SqlState `08001` + * @group Instances + */ + case object SqlClientUnableToEstablishSqlConnection extends SqlState("08001") + + /** + * SqlState `2F000` + * @group Instances + */ + case object SqlRoutineException extends SqlState("2F000") + + /** + * SqlState `08004` + * @group Instances + */ + case object SqlServerRejectedEstablishmentOfSqlConnection extends SqlState("08004") + + /** + * SqlState `03000` + * @group Instances + */ + case object SqlStatementNotYetComplete extends SqlState("03000") + + /** + * SqlState `39P02` + * @group Instances + */ + case object SrfProtocolViolated extends SqlState("39P02") + + /** + * SqlState `40003` + * @group Instances + */ + case object StatementCompletionUnknown extends SqlState("40003") + + /** + * SqlState `54001` + * @group Instances + */ + case object StatementTooComplex extends SqlState("54001") + + /** + * SqlState `22026` + * @group Instances + */ + case object StringDataLengthMismatch extends SqlState("22026") + + /** + * SqlState `22001` + * @group Instances + */ + case object StringDataRightTruncation extends SqlState("22001") + + /** + * SqlState `01004` + * @group Instances + */ + case object StringDataRightTruncation01 extends SqlState("01004") + + /** + * SqlState `22011` + * @group Instances + */ + case object SubstringError extends SqlState("22011") + + /** + * SqlState `00000` + * @group Instances + */ + case object SuccessfulCompletion extends SqlState("00000") + + /** + * SqlState `42601` + * @group Instances + */ + case object SyntaxError extends SqlState("42601") + + /** + * SqlState `42000` + * @group Instances + */ + case object SyntaxErrorOrAccessRuleViolation extends SqlState("42000") + + /** + * SqlState `54023` + * @group Instances + */ + case object TooManyArguments extends SqlState("54023") + + /** + * SqlState `54011` + * @group Instances + */ + case object TooManyColumns extends SqlState("54011") + + /** + * SqlState `53300` + * @group Instances + */ + case object TooManyConnections extends SqlState("53300") + + /** + * SqlState `P0003` + * @group Instances + */ + case object TooManyRows extends SqlState("P0003") + + /** + * SqlState `40002` + * @group Instances + */ + case object TransactionIntegrityConstraintViolation extends SqlState("40002") + + /** + * SqlState `08007` + * @group Instances + */ + case object TransactionResolutionUnknown extends SqlState("08007") + + /** + * SqlState `40000` + * @group Instances + */ + case object TransactionRollback extends SqlState("40000") + + /** + * SqlState `39P01` + * @group Instances + */ + case object TriggerProtocolViolated extends SqlState("39P01") + + /** + * SqlState `09000` + * @group Instances + */ + case object TriggeredActionException extends SqlState("09000") + + /** + * SqlState `27000` + * @group Instances + */ + case object TriggeredDataChangeViolation extends SqlState("27000") + + /** + * SqlState `22027` + * @group Instances + */ + case object TrimError extends SqlState("22027") + + /** + * SqlState `42703` + * @group Instances + */ + case object UndefinedColumn extends SqlState("42703") + + /** + * SqlState `58P01` + * @group Instances + */ + case object UndefinedFile extends SqlState("58P01") + + /** + * SqlState `42883` + * @group Instances + */ + case object UndefinedFunction extends SqlState("42883") + + /** + * SqlState `42704` + * @group Instances + */ + case object UndefinedObject extends SqlState("42704") + + /** + * SqlState `42P02` + * @group Instances + */ + case object UndefinedParameter extends SqlState("42P02") + + /** + * SqlState `42P01` + * @group Instances + */ + case object UndefinedTable extends SqlState("42P01") + + /** + * SqlState `23505` + * @group Instances + */ + case object UniqueViolation extends SqlState("23505") + + /** + * SqlState `22024` + * @group Instances + */ + case object UnterminatedCString extends SqlState("22024") + + /** + * SqlState `22P05` + * @group Instances + */ + case object UntranslatableCharacter extends SqlState("22P05") + + /** + * SqlState `01000` + * @group Instances + */ + case object Warning extends SqlState("01000") + + /** + * SqlState `42P20` + * @group Instances + */ + case object WindowingError extends SqlState("42P20") + + /** + * SqlState `44000` + * @group Instances + */ + case object WithCheckOptionViolation extends SqlState("44000") + + /** + * SqlState `42809` + * @group Instances + */ + case object WrongObjectType extends SqlState("42809") + + /** + * SqlState `2200F` + * @group Instances + */ + case object ZeroLengthCharacterString extends SqlState("2200F") + + val values: IndexedSeq[SqlState] = Vector( + ActiveSqlTransaction, + AdminShutdown, + AmbiguousAlias, + AmbiguousColumn, + AmbiguousFunction, + AmbiguousParameter, + ArraySubscriptError, + BadCopyFileFormat, + BranchTransactionAlreadyActive, + CannotCoerce, + CannotConnectNow, + CantChangeRuntimeParam, + CardinalityViolation, + CaseNotFound, + CharacterNotInRepertoire, + CheckViolation, + ConfigFileError, + ConnectionDoesNotExist, + ConnectionException, + ConnectionFailure, + ContainingSqlNotPermitted, + CrashShutdown, + DataCorrupted, + DataException, + DatabaseDropped, + DatatypeMismatch, + DatetimeFieldOverflow, + DeadlockDetected, + DependentObjectsStillExist, + DependentPrivilegeDescriptorsStillExist, + DeprecatedFeature, + DiskFull, + DivisionByZero, + DuplicateAlias, + DuplicateColumn, + DuplicateCursor, + DuplicateDatabase, + DuplicateFile, + DuplicateFunction, + DuplicateObject, + DuplicatePreparedStatement, + DuplicateSchema, + DuplicateTable, + DynamicResultSetsReturned, + ErrorInAssignment, + EscapeCharacterConflict, + ExclusionViolation, + ExternalRoutineException, + ExternalRoutineInvocationException, + FeatureNotSupported, + FloatingPointException, + ForeignKeyViolation, + FunctionExecutedNoReturnStatement, + GroupingError, + HeldCursorRequiresSameIsolationLevel, + ImplicitZeroBitPadding, + InFailedSqlTransaction, + InappropriateAccessModeForBranchTransaction, + InappropriateIsolationLevelForBranchTransaction, + IndeterminateDatatype, + IndexCorrupted, + IndicatorOverflow, + InsufficientPrivilege, + InsufficientResources, + IntegrityConstraintViolation, + InternalError, + IntervalFieldOverflow, + InvalidArgumentForLogarithm, + InvalidArgumentForNthValueFunction, + InvalidArgumentForNtileFunction, + InvalidArgumentForPowerFunction, + InvalidArgumentForWidthBucketFunction, + InvalidAuthorizationSpecification, + InvalidBinaryRepresentation, + InvalidCatalogName, + InvalidCharacterValueForCast, + InvalidColumnDefinition, + InvalidColumnReference, + InvalidCursorDefinition, + InvalidCursorName, + InvalidCursorState, + InvalidDatabaseDefinition, + InvalidDatetimeFormat, + InvalidEscapeCharacter, + InvalidEscapeOctet, + InvalidEscapeSequence, + InvalidForeignKey, + InvalidFunctionDefinition, + InvalidGrantOperation, + InvalidGrantor, + InvalidIndicatorParameterValue, + InvalidLocatorSpecification, + InvalidName, + InvalidObjectDefinition, + InvalidParameterValue, + InvalidPassword, + InvalidPreparedStatementDefinition, + InvalidRecursion, + InvalidRegularExpression, + InvalidRoleSpecification, + InvalidRowCountInLimitClause, + InvalidRowCountInResultOffsetClause, + InvalidSavepointSpecification, + InvalidSchemaDefinition, + InvalidSchemaName, + InvalidSqlStatementName, + InvalidSqlstateReturned, + InvalidTableDefinition, + InvalidTextRepresentation, + InvalidTimeZoneDisplacementValue, + InvalidTransactionInitiation, + InvalidTransactionState, + InvalidTransactionTermination, + InvalidUseOfEscapeCharacter, + InvalidXmlComment, + InvalidXmlContent, + InvalidXmlDocument, + InvalidXmlProcessingInstruction, + IoError, + LocatorException, + LockFileExists, + LockNotAvailable, + ModifyingSqlDataNotPermitted2F, + ModifyingSqlDataNotPermitted38, + MostSpecificTypeMismatch, + NameTooLong, + NoActiveSqlTransaction, + NoActiveSqlTransactionForBranchTransaction, + NoAdditionalDynamicResultSetsReturned, + NoData, + NoDataFound, + NonstandardUseOfEscapeCharacter, + NotAnXmlDocument, + NotNullViolation, + NullValueEliminatedInSetFunction, + NullValueNoIndicatorParameter, + NullValueNotAllowed, + NullValueNotAllowed39, + NumericValueOutOfRange, + ObjectInUse, + ObjectNotInPrerequisiteState, + OperatorIntervention, + OutOfMemory, + PlpgsqlError, + PrivilegeNotGranted, + PrivilegeNotRevoked, + ProgramLimitExceeded, + ProhibitedSqlStatementAttempted2F, + ProhibitedSqlStatementAttempted38, + ProtocolViolation, + QueryCanceled, + RaiseException, + ReadOnlySqlTransaction, + ReadingSqlDataNotPermitted2F, + ReadingSqlDataNotPermitted38, + ReservedName, + RestrictViolation, + SavepointException, + SchemaAndDataStatementMixingNotSupported, + SerializationFailure, + SqlClientUnableToEstablishSqlConnection, + SqlRoutineException, + SqlServerRejectedEstablishmentOfSqlConnection, + SqlStatementNotYetComplete, + SrfProtocolViolated, + StatementCompletionUnknown, + StatementTooComplex, + StringDataLengthMismatch, + StringDataRightTruncation, + StringDataRightTruncation01, + SubstringError, + SuccessfulCompletion, + SyntaxError, + SyntaxErrorOrAccessRuleViolation, + TooManyArguments, + TooManyColumns, + TooManyConnections, + TooManyRows, + TransactionIntegrityConstraintViolation, + TransactionResolutionUnknown, + TransactionRollback, + TriggerProtocolViolated, + TriggeredActionException, + TriggeredDataChangeViolation, + TrimError, + UndefinedColumn, + UndefinedFile, + UndefinedFunction, + UndefinedObject, + UndefinedParameter, + UndefinedTable, + UniqueViolation, + UnterminatedCString, + UntranslatableCharacter, + Warning, + WindowingError, + WithCheckOptionViolation, + WrongObjectType, + ZeroLengthCharacterString, + ) + +} \ No newline at end of file diff --git a/modules/core/native/src/main/scala-2/codec/EnumCodecPlatform.scala b/modules/core/native/src/main/scala-2/codec/EnumCodecPlatform.scala new file mode 100644 index 00000000..20674360 --- /dev/null +++ b/modules/core/native/src/main/scala-2/codec/EnumCodecPlatform.scala @@ -0,0 +1,8 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package skunk +package codec + +trait EnumCodecPlatform diff --git a/modules/core/native/src/main/scala/net/message/PasswordMessagePlatform.scala b/modules/core/native/src/main/scala/net/message/PasswordMessagePlatform.scala new file mode 100644 index 00000000..e5fca60b --- /dev/null +++ b/modules/core/native/src/main/scala/net/message/PasswordMessagePlatform.scala @@ -0,0 +1,72 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package skunk.net.message + +import scodec.bits.ByteVector + +import scala.scalanative.unsafe._ +import scala.scalanative.unsigned._ + +import openssl._ + +private[message] trait PasswordMessagePlatform { + + // See https://www.postgresql.org/docs/9.6/protocol-flow.html#AEN113418 + // and https://github.com/pgjdbc/pgjdbc/blob/master/pgjdbc/src/main/java/org/postgresql/util/MD5Digest.java + def md5(user: String, password: String, salt: Array[Byte]): PasswordMessage = Zone { implicit z => + + // Hash with this thing + val ctx = EVP_MD_CTX_new() + if (ctx == null) + throw new RuntimeException("EVP_MD_CTX_new") + + val `type` = EVP_get_digestbyname(c"MD5") + if (`type` == null) + throw new RuntimeException("EVP_get_digestbyname") + + try { + + val md = stackalloc[Byte](EVP_MAX_MD_SIZE) + val size = stackalloc[CUnsignedInt]() + + // First round + if (EVP_DigestInit_ex(ctx, `type`, null) != 1) + throw new RuntimeException("EVP_DigestInit_ex") + if (EVP_DigestUpdate(ctx, toCString(password), password.length.toULong) != 1) + throw new RuntimeException("EVP_DigestUpdate") + if (EVP_DigestUpdate(ctx, toCString(user), user.length.toULong) != 1) + throw new RuntimeException("EVP_DigestUpdate") + if (EVP_DigestFinal_ex(ctx, md, size) != 1) + throw new RuntimeException("EVP_DigestFinal_ex") + var hex = BigInt(1, ByteVector.view(md, (!size).toLong).toArray).toString(16) + while (hex.length < 32) + hex = "0" + hex + + if (EVP_MD_CTX_reset(ctx) != 1) + throw new RuntimeException("EVP_MD_CTX_reset") + + // Second round + if (EVP_DigestInit_ex(ctx, `type`, null) != 1) + throw new RuntimeException("EVP_DigestInit_ex") + if (EVP_DigestUpdate(ctx, toCString(hex), 32.toULong) != 1) + throw new RuntimeException("EVP_DigestUpdate") + if (EVP_DigestUpdate(ctx, ByteVector.view(salt).toPtr, salt.length.toULong) != 1) + throw new RuntimeException("EVP_DigestUpdate") + if (EVP_DigestFinal_ex(ctx, md, size) != 1) + throw new RuntimeException("EVP_DigestFinal_ex") + hex = BigInt(1, ByteVector.view(md, (!size).toLong).toArray).toString(16) + while (hex.length < 32) + hex = "0" + hex + + // Done + new PasswordMessage("md5" + hex) {} + + } finally { + openssl.EVP_MD_CTX_free(ctx) + } + + } + +} diff --git a/modules/core/native/src/main/scala/net/message/ScramPlatform.scala b/modules/core/native/src/main/scala/net/message/ScramPlatform.scala new file mode 100644 index 00000000..b19eb2f4 --- /dev/null +++ b/modules/core/native/src/main/scala/net/message/ScramPlatform.scala @@ -0,0 +1,56 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package skunk.net.message + +import scodec.bits.ByteVector + +import scala.scalanative.unsafe._ +import scala.scalanative.unsigned._ + +import openssl._ + +private[message] trait ScramPlatform { this: Scram.type => + + def clientFirstBareWithRandomNonce: ByteVector = { + val buf = stackalloc[Byte](32) + if (RAND_bytes(buf, 32) != 1) + throw new RuntimeException("RAND_bytes") + val nonce = ByteVector.view(buf, 32).toBase64 + clientFirstBareWithNonce(nonce) + } + + private[message] def HMAC(key: ByteVector, str: ByteVector): ByteVector = Zone { implicit z => + val evpMd = EVP_get_digestbyname(c"SHA256") + if (evpMd == null) + throw new RuntimeException("EVP_get_digestbyname") + val md = stackalloc[Byte](EVP_MAX_MD_SIZE) + val mdLen = stackalloc[CUnsignedInt]() + if (openssl.HMAC(evpMd, key.toPtr, key.size.toInt, str.toPtr, str.size.toULong, md, mdLen) == null) + throw new RuntimeException("HMAC") + ByteVector.fromPtr(md.asInstanceOf[Ptr[Byte]], (!mdLen).toLong) + } + + private[message] def H(input: ByteVector): ByteVector = Zone { implicit z => + val md = stackalloc[Byte](EVP_MAX_MD_SIZE) + val size = stackalloc[CUnsignedInt]() + val `type` = EVP_get_digestbyname(c"SHA256") + if (`type` == null) + throw new RuntimeException("EVP_get_digestbyname") + if (EVP_Digest(input.toPtr, input.size.toULong, md, size, `type`, null) != 1) + throw new RuntimeException("EVP_Digest") + ByteVector.fromPtr(md, (!size).toLong) + } + + private[message] def Hi(str: String, salt: ByteVector, iterations: Int): ByteVector = Zone { implicit z => + val digest = EVP_get_digestbyname(c"SHA256") + if (digest == null) + throw new RuntimeException("EVP_get_digestbyname") + val out = stackalloc[Byte](32) + if (PKCS5_PBKDF2_HMAC(toCString(str), str.length, salt.toPtr, salt.size.toInt, iterations, digest, 32, out) != 1) + throw new RuntimeException("PKCS5_PBKDF2_HMAC") + ByteVector.fromPtr(out, 32) + } + +} diff --git a/modules/core/native/src/main/scala/net/message/openssl.scala b/modules/core/native/src/main/scala/net/message/openssl.scala new file mode 100644 index 00000000..4efc9273 --- /dev/null +++ b/modules/core/native/src/main/scala/net/message/openssl.scala @@ -0,0 +1,63 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package skunk.net.message + +import scala.scalanative.unsafe._ + +@link("crypto") +@extern +private[message] object openssl { + + final val EVP_MAX_MD_SIZE = 64 + + type EVP_MD + type EVP_MD_CTX + type ENGINE + + def EVP_get_digestbyname(name: Ptr[CChar]): Ptr[EVP_MD] = extern + + def EVP_MD_CTX_new(): Ptr[EVP_MD_CTX] = extern + def EVP_MD_CTX_reset(ctx: Ptr[EVP_MD_CTX]): CInt = extern + def EVP_MD_CTX_free(ctx: Ptr[EVP_MD_CTX]): Unit = extern + + def EVP_DigestInit_ex(ctx: Ptr[EVP_MD_CTX], `type`: Ptr[EVP_MD], impl: Ptr[ENGINE]): CInt = extern + def EVP_DigestUpdate(ctx: Ptr[EVP_MD_CTX], d: Ptr[Byte], cnt: CSize): CInt = extern + def EVP_DigestFinal_ex(ctx: Ptr[EVP_MD_CTX], md: Ptr[Byte], s: Ptr[CUnsignedInt]): CInt = extern + def EVP_Digest( + data: Ptr[Byte], + count: CSize, + md: Ptr[Byte], + size: Ptr[CUnsignedInt], + `type`: Ptr[EVP_MD], + impl: Ptr[ENGINE] + ): CInt = extern + + def HMAC( + evp_md: Ptr[EVP_MD], + key: Ptr[Byte], + key_len: Int, + d: Ptr[Byte], + n: CSize, + md: Ptr[Byte], + md_len: Ptr[CUnsignedInt] + ): Ptr[CUnsignedChar] = extern + + def PKCS5_PBKDF2_HMAC( + pass: Ptr[CChar], + passlen: CInt, + salt: Ptr[Byte], + saltlen: CInt, + iter: CInt, + digest: Ptr[EVP_MD], + keylen: CInt, + out: Ptr[Byte] + ): CInt = extern + + def RAND_bytes( + buf: Ptr[CChar], + num: CInt + ): CInt = extern + +} diff --git a/modules/core/shared/src/main/scala-3/syntax/StringContextOps.scala b/modules/core/shared/src/main/scala-3/syntax/StringContextOps.scala index f8e42136..b6c741bf 100644 --- a/modules/core/shared/src/main/scala-3/syntax/StringContextOps.scala +++ b/modules/core/shared/src/main/scala-3/syntax/StringContextOps.scala @@ -61,77 +61,79 @@ object StringContextOps { // Our prefix looks like this, and the stringy parts of the interpolation will be a non-empty // list of string expressions. We just know this because of the way interpolator desugaring // works. If it doesn't work something bad has happened. - val strings: List[String] = + val strings: Either[Expr[Any], List[String]] = sc match { - case '{ StringContext(${Varargs(Exprs(parts))}: _*) } => parts.toList + case '{ StringContext(${Varargs(Exprs(parts))}: _*) } => Right(parts.toList) case _ => - report.error(s"StringContext arguments must be literals.") - return '{???} + Left('{ compiletime.error(s"StringContext arguments must be literals.") }) } // The interpolated args are a list of size `parts.length - 1`. We also just know this. val args: List[Expr[Any]] = { - val Varargs(args) = argsExpr + val Varargs(args) = argsExpr: @unchecked // we just know this. right? args.toList } // Weave the strings and args together, and accumulate a single encoder. - val lastPart: Expr[Part] = '{Str(${Expr(strings.last)})} - val (parts, encoders): (List[Expr[Part]], List[Expr[Any]]) = - (strings zip args).foldRight((List[Expr[Part]](lastPart), List.empty[Expr[Any]])) { + val partsEncoders: Either[Expr[Any], (List[Expr[Part]], List[Expr[Any]])] = strings.flatMap { strings => + val lastPart: Expr[Part] = '{Str(${Expr(strings.last)})} + (strings zip args).reverse.foldLeftM((List[Expr[Part]](lastPart), List.empty[Expr[Any]])) { - case ((str, arg), (parts, es)) => + case ((parts, es), (str, arg)) => - if (str.endsWith("#")) { + if (str.endsWith("#")) { - // Interpolations like "...#$foo ..." require `foo` to be a String. - arg match { - case '{ $s: String } => ('{Str(${Expr(str.dropRight(1))})} :: '{Str($s)} :: parts, es) - case '{ $a: t } => + // Interpolations like "...#$foo ..." require `foo` to be a String. + arg match { + case '{ $s: String } => Right(('{Str(${Expr(str.dropRight(1))})} :: '{Str($s)} :: parts, es)) + case '{ $a: t } => report.error(s"Found ${Type.show[t]}, expected String.}", a) - return '{???} /// + Left('{ compiletime.error("Expected String") }) /// } - } else { + } else { - arg match { + arg match { - // The interpolated thing is an Encoder. - case '{ $e: Encoder[t] } => - val newParts = '{Str(${Expr(str)})} :: '{Par($e.sql)} :: parts - val newEncoders = '{ $e : Encoder[t] } :: es - (newParts, newEncoders) + // The interpolated thing is an Encoder. + case '{ $e: Encoder[t] } => + val newParts = '{Str(${Expr(str)})} :: '{Par($e.sql)} :: parts + val newEncoders = '{ $e : Encoder[t] } :: es + Right((newParts, newEncoders)) - // The interpolated thing is a Fragment[Void] - case '{ $f: Fragment[Void] } => - val newParts = '{Str(${Expr(str)})} :: '{Emb($f.parts)} :: parts - (newParts, es) + // The interpolated thing is a Fragment[Void] + case '{ $f: Fragment[Void] } => + val newParts = '{Str(${Expr(str)})} :: '{Emb($f.parts)} :: parts + Right((newParts, es)) - // The interpolated thing is a Fragment[A] for some A other than Void - case '{ $f: Fragment[a] } => - val newParts = '{Str(${Expr(str)})} :: '{Emb($f.parts)} :: parts - val newEncoders = '{ $f.encoder : Encoder[a] } :: es - (newParts, newEncoders) + // The interpolated thing is a Fragment[A] for some A other than Void + case '{ $f: Fragment[a] } => + val newParts = '{Str(${Expr(str)})} :: '{Emb($f.parts)} :: parts + val newEncoders = '{ $f.encoder : Encoder[a] } :: es + Right((newParts, newEncoders)) - case '{ $a: t } => + case '{ $a: t } => report.error(s"Found ${Type.show[t]}, expected String, Encoder, or Fragment.", a) - return '{???} + Left('{compiletime.error("Expected String, Encoder, or Fragment.")}) - } + } - } + } + } } - val finalEnc: Expr[Any] = - if (encoders.isEmpty) '{ Void.codec } - else encoders.reduceLeft { - case ('{$a : Encoder[a]}, '{ $b : Encoder[b] }) => '{$a ~ $b} - } + partsEncoders.map { (parts, encoders) => + val finalEnc: Expr[Any] = + if (encoders.isEmpty) '{ Void.codec } + else encoders.reduceLeft { + case ('{$a : Encoder[a]}, '{ $b : Encoder[b] }) => '{$a ~ $b} + } - finalEnc match { - case '{ $e : Encoder[t] } => '{ fragmentFromParts[t](${Expr.ofList(parts)}, $e, $origin) } - } + finalEnc match { + case '{ $e : Encoder[t] } => '{ fragmentFromParts[t](${Expr.ofList(parts)}, $e, $origin) } + } + }.merge } diff --git a/modules/core/shared/src/main/scala/SSL.scala b/modules/core/shared/src/main/scala/SSL.scala index 19efcfc0..ca0c0eb6 100644 --- a/modules/core/shared/src/main/scala/SSL.scala +++ b/modules/core/shared/src/main/scala/SSL.scala @@ -5,6 +5,7 @@ package skunk import cats._ +import cats.effect.Resource import cats.syntax.all._ import fs2.io.net.Network import fs2.io.net.tls.TLSContext @@ -16,25 +17,25 @@ abstract class SSL private[skunk] ( val fallbackOk: Boolean = false, ) { outer => - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] def withTLSParameters(tlsParameters: TLSParameters): SSL = new SSL(tlsParameters, fallbackOk) { - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] = + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] = outer.tlsContext } def withFallback(fallbackOk: Boolean): SSL = new SSL(tlsParameters, fallbackOk) { - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] = + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] = outer.tlsContext } def toSSLNegotiationOptions[F[_]: Network](logger: Option[String => F[Unit]])( implicit ev: ApplicativeError[F, Throwable] - ): F[Option[SSLNegotiation.Options[F]]] = + ): Resource[F, Option[SSLNegotiation.Options[F]]] = this match { - case SSL.None => none.pure[F] + case SSL.None => Resource.pure(None) case _ => tlsContext.map(SSLNegotiation.Options(_, tlsParameters, fallbackOk, logger).some) } @@ -44,22 +45,22 @@ object SSL extends SSLCompanionPlatform { /** `SSL` which indicates that SSL is not to be used. */ object None extends SSL { - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] = - ev.raiseError(new Exception("SSL.None: cannot create a TLSContext.")) + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] = + Resource.eval(ev.raiseError(new Exception("SSL.None: cannot create a TLSContext."))) override def withFallback(fallbackOk: Boolean): SSL = this override def withTLSParameters(tlsParameters: TLSParameters): SSL = this } /** `SSL` which trusts all certificates. */ object Trusted extends SSL { - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] = - Network[F].tlsContext.insecure + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] = + Network[F].tlsContext.insecureResource } /** `SSL` from the system default `SSLContext`. */ object System extends SSL { - def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): F[TLSContext[F]] = - Network[F].tlsContext.system + def tlsContext[F[_]: Network](implicit ev: ApplicativeError[F, Throwable]): Resource[F, TLSContext[F]] = + Network[F].tlsContext.systemResource } } diff --git a/modules/core/shared/src/main/scala/Session.scala b/modules/core/shared/src/main/scala/Session.scala index c4a84fe1..7150f677 100644 --- a/modules/core/shared/src/main/scala/Session.scala +++ b/modules/core/shared/src/main/scala/Session.scala @@ -287,7 +287,7 @@ object Session { for { dc <- Resource.eval(Describe.Cache.empty[F](commandCache, queryCache)) - sslOp <- Resource.eval(ssl.toSSLNegotiationOptions(if (debug) logger.some else none)) + sslOp <- ssl.toSSLNegotiationOptions(if (debug) logger.some else none) pool <- Pool.of(session(Network[F], sslOp, dc), max)(Recyclers.full) } yield pool diff --git a/modules/core/shared/src/main/scala/data/Completion.scala b/modules/core/shared/src/main/scala/data/Completion.scala index 238c8c0a..4c8aaa70 100644 --- a/modules/core/shared/src/main/scala/data/Completion.scala +++ b/modules/core/shared/src/main/scala/data/Completion.scala @@ -44,6 +44,8 @@ object Completion { case object DropSequence extends Completion case object CreateDatabase extends Completion case object DropDatabase extends Completion + case object CreateRole extends Completion + case object DropRole extends Completion // more ... /** diff --git a/modules/core/shared/src/main/scala/net/message/CommandComplete.scala b/modules/core/shared/src/main/scala/net/message/CommandComplete.scala index ce6cb461..134d5d77 100644 --- a/modules/core/shared/src/main/scala/net/message/CommandComplete.scala +++ b/modules/core/shared/src/main/scala/net/message/CommandComplete.scala @@ -77,6 +77,8 @@ object CommandComplete { case "DROP SEQUENCE" => apply(Completion.DropSequence) case "CREATE DATABASE" => apply(Completion.CreateDatabase) case "DROP DATABASE" => apply(Completion.DropDatabase) + case "CREATE ROLE" => apply(Completion.CreateRole) + case "DROP ROLE" => apply(Completion.DropRole) case Patterns.Select(s) => apply(Completion.Select(s.toInt)) case Patterns.Delete(s) => apply(Completion.Delete(s.toInt)) case Patterns.Update(s) => apply(Completion.Update(s.toInt)) diff --git a/modules/tests/shared/src/test/scala-2/codec/EnumCodecTest.scala b/modules/tests/js-jvm/src/test/scala-2/codec/EnumCodecTest.scala similarity index 100% rename from modules/tests/shared/src/test/scala-2/codec/EnumCodecTest.scala rename to modules/tests/js-jvm/src/test/scala-2/codec/EnumCodecTest.scala diff --git a/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala b/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala index 915ff897..16acda01 100644 --- a/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala +++ b/modules/tests/jvm/src/main/scala/ffstest/FFrameworkPlatform.scala @@ -6,20 +6,4 @@ package ffstest import munit.CatsEffectSuite -import java.util.concurrent.Executors -import scala.concurrent.ExecutionContext - -trait FTestPlatform extends CatsEffectSuite { - - // ensure that we have bountiful threads - val executor = Executors.newCachedThreadPool() - - override val munitExecutionContext: ExecutionContext = - ExecutionContext.fromExecutor(executor) - - override def afterAll(): Unit = { - super.afterAll(); - executor.shutdown() - } - -} +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 new file mode 100644 index 00000000..d1db0352 --- /dev/null +++ b/modules/tests/native/src/main/scala/ffstest/FFrameworkPlatform.scala @@ -0,0 +1,12 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package ffstest + +import epollcat.unsafe.EpollRuntime +import munit.CatsEffectSuite + +trait FTestPlatform extends CatsEffectSuite { + override def munitIORuntime = EpollRuntime.global +} \ No newline at end of file diff --git a/modules/tests/shared/src/test/scala/CommandTest.scala b/modules/tests/shared/src/test/scala/CommandTest.scala index 980f7494..7aef59ce 100644 --- a/modules/tests/shared/src/test/scala/CommandTest.scala +++ b/modules/tests/shared/src/test/scala/CommandTest.scala @@ -163,6 +163,16 @@ class CommandTest extends SkunkTest { DROP DATABASE IF EXISTS skunk_database """.command + val createRole: Command[Void] = + sql""" + CREATE ROLE skunk_role + """.command + + val dropRole: Command[Void] = + sql""" + DROP ROLE skunk_role + """.command + sessionTest("create table, create index, drop index, alter table and drop table") { s => for { c <- s.execute(createTable) @@ -231,6 +241,15 @@ class CommandTest extends SkunkTest { } yield "ok" } + sessionTest("create role, drop role") { s => + for { + c <- s.execute(createRole) + _ <- assert("completion", c == Completion.CreateRole) + c <- s.execute(dropRole) + _ <- assert("completion", c == Completion.DropRole) + } yield "ok" + } + sessionTest("do command"){ s=> for{ c <- s.execute(doCommand) diff --git a/modules/tests/shared/src/test/scala/SslTest.scala b/modules/tests/shared/src/test/scala/SslTest.scala index 24723535..17c10aaa 100644 --- a/modules/tests/shared/src/test/scala/SslTest.scala +++ b/modules/tests/shared/src/test/scala/SslTest.scala @@ -79,7 +79,7 @@ class SslTest extends ffstest.FTest { test("SSL.None cannot produce an SSLContext") { for { - ex <- SSL.None.tlsContext[IO].assertFailsWith[Exception] + ex <- SSL.None.tlsContext[IO].use_.assertFailsWith[Exception] _ <- assertEqual("failure message", ex.getMessage, "SSL.None: cannot create a TLSContext.") } yield "ok" } diff --git a/modules/tests/shared/src/test/scala/StartupTest.scala b/modules/tests/shared/src/test/scala/StartupTest.scala index c43074f3..21beec06 100644 --- a/modules/tests/shared/src/test/scala/StartupTest.scala +++ b/modules/tests/shared/src/test/scala/StartupTest.scala @@ -5,13 +5,14 @@ package tests import cats.effect._ -import com.comcast.ip4s.UnknownHostException import fs2.io.net.ConnectException import natchez.Trace.Implicits.noop import skunk._ import skunk.exception.SkunkException import skunk.exception.StartupException +import java.io.IOException + class StartupTest extends ffstest.FTest { // Different ports for different authentication schemes. @@ -253,6 +254,6 @@ class StartupTest extends ffstest.FTest { host = "blergh", user = "bob", database = "nobody cares", - ).use(_ => IO.unit).assertFailsWith[UnknownHostException] + ).use(_ => IO.unit).assertFailsWith[IOException] // ideally an `UnknownHostException` } } diff --git a/modules/tests/shared/src/test/scala-2/codec/TemporalCodecTest.scala b/modules/tests/shared/src/test/scala/codec/TemporalCodecTest.scala similarity index 98% rename from modules/tests/shared/src/test/scala-2/codec/TemporalCodecTest.scala rename to modules/tests/shared/src/test/scala/codec/TemporalCodecTest.scala index 2f71d1ea..6c16f94b 100644 --- a/modules/tests/shared/src/test/scala-2/codec/TemporalCodecTest.scala +++ b/modules/tests/shared/src/test/scala/codec/TemporalCodecTest.scala @@ -7,7 +7,7 @@ package codec import cats.Eq import cats.syntax.all._ -import io.chrisdavenport.cats.time.{ offsetdatetimeInstances => _, _ } +import org.typelevel.cats.time.{ offsetdatetimeInstances => _, _ } import java.time._ import skunk.codec.temporal._ import cats.effect.{IO, Resource} diff --git a/project/build.properties b/project/build.properties index 22af2628..8b9a0b0a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.7.1 +sbt.version=1.8.0 diff --git a/project/plugins.sbt b/project/plugins.sbt index 908115a0..d748933b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,11 +3,14 @@ ThisBuild / libraryDependencySchemes ++= Seq( "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always ) -addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.4.15") -addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.10.2") +addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.4.17") +addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.10.3") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") -addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.3") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.3") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.2") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.6") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.6") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.11.0") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.7") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.2.0") +addSbtPlugin("com.armanbilge" % "sbt-scala-native-config-brew-github-actions" % "0.1.2")