diff --git a/build.sbt b/build.sbt index a892e237c..dd5668db2 100644 --- a/build.sbt +++ b/build.sbt @@ -2,10 +2,10 @@ import FreeGen2._ import sbt.dsl.LinterLevel.Ignore // Library versions all in one place, for convenience and sanity. -lazy val catsVersion = "2.4.2" -lazy val catsEffectVersion = "3.0.0" +lazy val catsVersion = "2.5.0" +lazy val catsEffectVersion = "3.0.2" lazy val circeVersion = "0.13.0" -lazy val fs2Version = "3.0.0" +lazy val fs2Version = "3.0.1" lazy val h2Version = "1.4.200" lazy val hikariVersion = "3.4.5" // N.B. Hikari v4 introduces a breaking change via slf4j v2 lazy val kindProjectorVersion = "0.11.2" @@ -13,16 +13,16 @@ lazy val monixVersion = "3.3.0" lazy val quillVersion = "3.6.1" lazy val postGisVersion = "2.5.0" lazy val postgresVersion = "42.2.19" -lazy val refinedVersion = "0.9.19" +lazy val refinedVersion = "0.9.23" lazy val scalaCheckVersion = "1.15.1" lazy val scalatestVersion = "3.2.7" -lazy val shapelessVersion = "2.3.3" +lazy val shapelessVersion = "2.3.4" lazy val silencerVersion = "1.7.1" lazy val specs2Version = "4.10.6" lazy val scala212Version = "2.12.12" lazy val scala213Version = "2.13.5" -lazy val scala30VersionOld = "3.0.0-M3" -lazy val scala30Version = "3.0.0-RC1" +lazy val scala30VersionOld = "3.0.0-RC1" +lazy val scala30Version = "3.0.0-RC2" lazy val slf4jVersion = "1.7.30" // These are releases to ignore during MiMa checks @@ -42,7 +42,7 @@ lazy val compilerFlags = Seq( "-Xfatal-warnings" ), libraryDependencies ++= Seq( - "org.scala-lang.modules" %% "scala-collection-compat" % "2.4.2" + "org.scala-lang.modules" %% "scala-collection-compat" % "2.4.3" ) ) @@ -80,8 +80,8 @@ lazy val commonSettings = // MUnit libraryDependencies ++= Seq( - "org.typelevel" %% "scalacheck-effect-munit" % "0.7.1" % Test, - "org.typelevel" %% "munit-cats-effect-2" % "0.13.1" % Test, + "org.typelevel" %% "scalacheck-effect-munit" % "1.0.0" % Test, + "org.typelevel" %% "munit-cats-effect-2" % "1.0.1" % Test, ), testFrameworks += new TestFramework("munit.Framework"), @@ -220,7 +220,7 @@ lazy val core = project libraryDependencies ++= Seq( "com.chuusai" %% "shapeless" % shapelessVersion, ).filterNot(_ => isDotty.value) ++ Seq( - "org.tpolecat" %% "typename" % "0.1.5", + "org.tpolecat" %% "typename" % "0.1.6", "com.h2database" % "h2" % h2Version % "test", ), scalacOptions += "-Yno-predef", @@ -460,7 +460,7 @@ lazy val refined = project name := "doobie-refined", description := "Refined support for doobie.", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.21", + "eu.timepit" %% "refined" % refinedVersion, "com.h2database" % "h2" % h2Version % "test" ) ) diff --git a/modules/core/src/main/scala/doobie/util/fragments.scala b/modules/core/src/main/scala/doobie/util/fragments.scala index 013194171..815c41eb2 100644 --- a/modules/core/src/main/scala/doobie/util/fragments.scala +++ b/modules/core/src/main/scala/doobie/util/fragments.scala @@ -12,6 +12,10 @@ import cats.syntax.all._ /** Module of `Fragment` constructors. */ object fragments { + /** Returns `VALUES (fs0), (fs1), ...`. */ + def values[F[_]: Reducible, A](fs: F[A])(implicit w: util.Write[A]): Fragment = + fs.toList.map(a => fr0"(${w.toFragment(a)})").foldSmash1(fr0"VALUES ", fr",", fr"") + /** Returns `f IN (fs0, fs1, ...)`. */ def in[F[_]: Reducible, A: util.Put](f: Fragment, fs: F[A]): Fragment = fs.toList.map(a => fr0"$a").foldSmash1(f ++ fr0"IN (", fr",", fr")") diff --git a/modules/core/src/test/scala/doobie/util/FragmentsSuite.scala b/modules/core/src/test/scala/doobie/util/FragmentsSuite.scala index c32a3328f..808c3e8a8 100644 --- a/modules/core/src/test/scala/doobie/util/FragmentsSuite.scala +++ b/modules/core/src/test/scala/doobie/util/FragmentsSuite.scala @@ -24,6 +24,14 @@ class FragmentsSuite extends munit.FunSuite { val fs = List(1,2,3).map(n => fr"$n") val ofs = List(1,2,3).map(n => Some(fr"$n").filter(_ => n % 2 =!= 0)) + test("values for one column") { + assertEquals(values(nel).query[Unit].sql, "VALUES (?), (?), (?) ") + } + + test("values for two columns") { + assertEquals(values(NonEmptyList.of((1, true), (2, false))).query[Unit].sql, "VALUES (?,?), (?,?) ") + } + test("in for one column") { assertEquals(in(fr"foo", nel).query[Unit].sql, "foo IN (?, ?, ?) ") } diff --git a/modules/docs/src/main/mdoc/docs/14-Managing-Connections.md b/modules/docs/src/main/mdoc/docs/14-Managing-Connections.md index b650e7d2a..08b3979fe 100644 --- a/modules/docs/src/main/mdoc/docs/14-Managing-Connections.md +++ b/modules/docs/src/main/mdoc/docs/14-Managing-Connections.md @@ -13,7 +13,7 @@ import doobie.implicits._ ### About Transactors -Most **doobie** programs are values of type `ConnectionIO[A]` or `Stream[ConnnectionIO, A]` that describe computations requiring a database connection. By providing a means of acquiring a JDBC connection we can transform these programs into computations that can actually be executed. The most common way of performing this transformation is via a `Transactor`. +Most **doobie** programs are values of type `ConnectionIO[A]` or `Stream[ConnectionIO, A]` that describe computations requiring a database connection. By providing a means of acquiring a JDBC connection we can transform these programs into computations that can actually be executed. The most common way of performing this transformation is via a `Transactor`. A `Transactor[M]` consists of the following bits of information: diff --git a/modules/docs/src/main/mdoc/index.md b/modules/docs/src/main/mdoc/index.md index f682bd2e9..5d9ab8d64 100644 --- a/modules/docs/src/main/mdoc/index.md +++ b/modules/docs/src/main/mdoc/index.md @@ -142,4 +142,5 @@ Don't see yours? [You can add it in a PR!](https://github.com/tpolecat/doobie/ed - [SecurityScorecard](https://securityscorecard.io) - [SoftwareMill](https://softwaremill.com) - [Unit](https://unit.co) + - [CurrencyCloud](https://www.currencycloud.com) diff --git a/modules/h2/src/main/scala/doobie/h2/Metas.scala b/modules/h2/src/main/scala/doobie/h2/Metas.scala index 24d361740..d3b5503ab 100644 --- a/modules/h2/src/main/scala/doobie/h2/Metas.scala +++ b/modules/h2/src/main/scala/doobie/h2/Metas.scala @@ -12,7 +12,6 @@ import scala.Predef._ import scala.reflect.ClassTag import cats.data.NonEmptyList.{ of => NonEmptyListOf } import doobie.util.meta.Meta -import org.tpolecat.typename._ trait Instances { @@ -34,7 +33,7 @@ trait Instances { // see postgres contrib for an explanation of array mapping; we may want to factor this out - private def boxedPair[A >: Null <: AnyRef: ClassTag: TypeName]: (Meta[Array[A]], Meta[Array[Option[A]]]) = { + private def boxedPair[A >: Null <: AnyRef: ClassTag]: (Meta[Array[A]], Meta[Array[Option[A]]]) = { val raw = Meta.Advanced.other[Array[Object]]("ARRAY").timap[Array[A]]( a => if (a == null) null else a.map(_.asInstanceOf[A]))( a => if (a == null) null else a.map(_.asInstanceOf[Object])) @@ -69,7 +68,7 @@ trait Instances { implicit val liftedStringArrayType: Meta[Array[Option[java.lang.String]]] = boxedStringPair._2 - private def unboxedPair[A >: Null <: AnyRef: ClassTag, B <: AnyVal: ClassTag: TypeName](f: A => B, g: B => A)( + private def unboxedPair[A >: Null <: AnyRef: ClassTag, B <: AnyVal: ClassTag](f: A => B, g: B => A)( implicit boxed: Meta[Array[A]], boxedLifted: Meta[Array[Option[A]]]): (Meta[Array[B]], Meta[Array[Option[B]]]) = (boxed.timap(a => if (a == null) null else a.map(f))(a => if (a == null) null else a.map(g)), boxedLifted.timap(_.asInstanceOf[Array[Option[B]]])(_.asInstanceOf[Array[Option[A]]])) diff --git a/modules/hikari/src/test/scala/doobie/hikari/issue/824.scala b/modules/hikari/src/test/scala/doobie/hikari/issue/824.scala index 18502fd61..3e0fe5adb 100644 --- a/modules/hikari/src/test/scala/doobie/hikari/issue/824.scala +++ b/modules/hikari/src/test/scala/doobie/hikari/issue/824.scala @@ -26,7 +26,7 @@ class `824` extends munit.FunSuite { "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", // connect URL "sa", // username "", // password - ce // await connection here + ce // await connection here ) } yield xa diff --git a/modules/postgres/src/main/scala/doobie/postgres/Instances.scala b/modules/postgres/src/main/scala/doobie/postgres/Instances.scala index 64230f359..f9c48544e 100644 --- a/modules/postgres/src/main/scala/doobie/postgres/Instances.scala +++ b/modules/postgres/src/main/scala/doobie/postgres/Instances.scala @@ -78,7 +78,7 @@ trait Instances { // of each 1-d array type. In the non-nullable case we simply check for nulls and perform a cast; // in the nullable case we must copy the array in both directions to lift/unlift Option. @SuppressWarnings(Array("org.wartremover.warts.Equals", "org.wartremover.warts.ArrayEquals", "org.wartremover.warts.Throw")) - private def boxedPair[A >: Null <: AnyRef: ClassTag: TypeName](elemType: String, arrayType: String, arrayTypeT: String*): (Meta[Array[A]], Meta[Array[Option[A]]]) = { + private def boxedPair[A >: Null <: AnyRef: ClassTag](elemType: String, arrayType: String, arrayTypeT: String*): (Meta[Array[A]], Meta[Array[Option[A]]]) = { val raw = Meta.Advanced.array[A](elemType, arrayType, arrayTypeT: _*) // Ensure `a`, which may be null, which is ok, contains no null elements. def checkNull[B >: Null](a: Array[B], e: Exception): Array[B] = @@ -130,7 +130,7 @@ trait Instances { // lifted case because the representation is identical, assuming no nulls. In the long run this // may need to become something slower but safer. Unclear. @SuppressWarnings(Array("org.wartremover.warts.Equals", "org.wartremover.warts.ArrayEquals", "org.wartremover.warts.AsInstanceOf")) - private def unboxedPair[A >: Null <: AnyRef: ClassTag, B <: AnyVal: ClassTag: TypeName](f: A => B, g: B => A)( + private def unboxedPair[A >: Null <: AnyRef: ClassTag, B <: AnyVal: ClassTag](f: A => B, g: B => A)( implicit boxed: Meta[Array[A]], boxedLifted: Meta[Array[Option[A]]]): (Meta[Array[B]], Meta[Array[Option[B]]]) = // TODO: assert, somehow, that A is the boxed version of B so we catch errors on instance // construction, which is somewhat better than at [logical] execution time. diff --git a/modules/postgres/src/main/scala/doobie/postgres/Text.scala b/modules/postgres/src/main/scala/doobie/postgres/Text.scala index 7d3b26bb2..24872b18c 100644 --- a/modules/postgres/src/main/scala/doobie/postgres/Text.scala +++ b/modules/postgres/src/main/scala/doobie/postgres/Text.scala @@ -113,25 +113,26 @@ trait TextInstances extends TextInstances0 { this: Text.type => case c => stdChar(c, sb) } sb.append('"') + () } } //Char - implicit val charInstance: Text[Char] = instance((n, sb) => sb.append(n.toString)) + implicit val charInstance: Text[Char] = instance((n, sb) => {sb.append(n.toString); ()}) // Primitive Numerics - implicit val intInstance: Text[Int] = instance((n, sb) => sb.append(n)) - implicit val shortInstance: Text[Short] = instance((n, sb) => sb.append(n)) - implicit val longInstance: Text[Long] = instance((n, sb) => sb.append(n)) - implicit val floatInstance: Text[Float] = instance((n, sb) => sb.append(n)) - implicit val doubleInstance: Text[Double] = instance((n, sb) => sb.append(n)) + implicit val intInstance: Text[Int] = instance((n, sb) => {sb.append(n); ()}) + implicit val shortInstance: Text[Short] = instance((n, sb) => {sb.append(n); ()}) + implicit val longInstance: Text[Long] = instance((n, sb) => {sb.append(n); ()}) + implicit val floatInstance: Text[Float] = instance((n, sb) => {sb.append(n); ()}) + implicit val doubleInstance: Text[Double] = instance((n, sb) => {sb.append(n); ()}) // Big Numerics - implicit val bigDecimalInstance: Text[BigDecimal] = instance { (n, sb) => sb.append(n.toString) } + implicit val bigDecimalInstance: Text[BigDecimal] = instance { (n, sb) => {sb.append(n.toString); ()} } // Boolean implicit val booleanInstance: Text[Boolean] = - instance((b, sb) => sb.append(b)) + instance((b, sb) => {sb.append(b); ()}) // Date, Time, etc. @@ -145,6 +146,7 @@ trait TextInstances extends TextInstances0 { this: Text.type => val pad = bs.length * 2 - hex.length (0 until pad).foreach(_ => sb.append("0")) sb.append(hex) + () } } @@ -153,8 +155,8 @@ trait TextInstances extends TextInstances0 { this: Text.type => implicit csv: Text[A] ): Text[Option[A]] = instance { - case (Some(a), sb) => csv.unsafeEncode(a, sb) - case (None, sb) => sb.append(Text.NULL) + case (Some(a), sb) => {csv.unsafeEncode(a, sb); ()} + case (None, sb) => {sb.append(Text.NULL); ()} } } @@ -176,6 +178,7 @@ trait TextInstances0 extends TextInstances1 { this: Text.type => ev.unsafeArrayEncode(a, sb) } sb.append('}') + () } } diff --git a/modules/postgres/src/main/scala/doobie/postgres/free/kleisliinterpreter.scala b/modules/postgres/src/main/scala/doobie/postgres/free/kleisliinterpreter.scala index 8d0e28969..1cad1cb66 100644 --- a/modules/postgres/src/main/scala/doobie/postgres/free/kleisliinterpreter.scala +++ b/modules/postgres/src/main/scala/doobie/postgres/free/kleisliinterpreter.scala @@ -18,21 +18,13 @@ import java.io.InputStream import java.io.OutputStream import java.io.Reader import java.io.Writer -import java.lang.Class -import java.lang.String -import java.sql.{ Array => SqlArray } -import java.util.Map import org.postgresql.PGConnection -import org.postgresql.PGNotification -import org.postgresql.copy.{ CopyDual => PGCopyDual } import org.postgresql.copy.{ CopyIn => PGCopyIn } import org.postgresql.copy.{ CopyManager => PGCopyManager } import org.postgresql.copy.{ CopyOut => PGCopyOut } import org.postgresql.jdbc.AutoSave -import org.postgresql.jdbc.PreferQueryMode import org.postgresql.largeobject.LargeObject import org.postgresql.largeobject.LargeObjectManager -import org.postgresql.replication.PGReplicationConnection import org.postgresql.util.ByteStreamWriter // Algebras and free monads thereof referenced by our interpreter. diff --git a/modules/postgres/src/main/scala/doobie/postgres/syntax/FragmentSyntax.scala b/modules/postgres/src/main/scala/doobie/postgres/syntax/FragmentSyntax.scala index fe575c1b4..e22d352e6 100644 --- a/modules/postgres/src/main/scala/doobie/postgres/syntax/FragmentSyntax.scala +++ b/modules/postgres/src/main/scala/doobie/postgres/syntax/FragmentSyntax.scala @@ -49,12 +49,12 @@ class FragmentOps(f: Fragment) { Stream.bracketCase( PHC.pgGetCopyAPI(PFCM.copyIn(f.query.sql)) ){ - case (copyIn, Resource.ExitCase.Succeeded) => + case (copyIn, Resource.ExitCase.Succeeded) => PHC.embed(copyIn, PFCI.isActive.ifM(PFCI.endCopy.void, PFCI.unit)) - case (copyIn, _) => + case (copyIn, _) => PHC.embed(copyIn, PFCI.cancelCopy) }.flatMap(copyIn => - byteStream.chunks.evalMap(bytes => + byteStream.chunks.evalMap(bytes => PHC.embed(copyIn, PFCI.writeToCopy(bytes.toArray, 0, bytes.size)) ) *> Stream.eval(PHC.embed(copyIn, PFCI.endCopy)) diff --git a/project/plugins.sbt b/project/plugins.sbt index 443dd6469..6f4d7fba3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,7 +3,7 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") -addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.2") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.3") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.0") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.6.0") addSbtPlugin("io.chrisdavenport" % "sbt-mima-version-check" % "0.1.2") diff --git a/project/project/plugins.sbt b/project/project/plugins.sbt index aa4d3f066..2e5f87615 100644 --- a/project/project/plugins.sbt +++ b/project/project/plugins.sbt @@ -1 +1 @@ -addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.2") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.3")