From 514c03ccd0855067abbb3b917ea01d70ab792f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Wed, 17 Aug 2022 21:25:15 +0200 Subject: [PATCH] Migration RowParserImpl --- .github/workflows/build-test.yml | 4 +- .scalafix.conf | 2 +- .scalafmt.conf | 10 +- .../src/test/scala/anorm/AkkaStreamSpec.scala | 18 +- build.sbt | 103 ++++-- core/src/main/scala-2/anorm/Macro.scala | 5 +- .../anorm/macros/SealedRowParserImpl.scala | 6 +- .../anorm/macros/ToParameterListImpl.scala | 5 +- core/src/main/scala-3/anorm/Macro.scala | 128 ++++--- .../scala-3/anorm/macros/RowParserImpl.scala | 335 ++++++++++++++++++ .../anorm/macros/SealedRowParserImpl.scala | 25 +- .../anorm/macros/ToParameterListImpl.scala | 44 ++- core/src/main/scala/anorm/Anorm.scala | 3 +- core/src/main/scala/anorm/Column.scala | 8 +- .../src/main/scala/anorm/NamedParameter.scala | 14 +- core/src/main/scala/anorm/Row.scala | 6 +- core/src/main/scala/anorm/RowParser.scala | 180 ++++++++++ core/src/main/scala/anorm/SimpleSql.scala | 11 +- core/src/main/scala/anorm/SqlParser.scala | 239 ++----------- core/src/main/scala/anorm/SqlResult.scala | 14 + .../main/scala/anorm/ToStatementMisc.scala | 168 ++++++--- core/src/main/scala/anorm/package.scala | 2 +- .../test/scala-2/anorm/AnormCompatSpec.scala | 3 +- core/src/test/scala/anorm/AnormSpec.scala | 2 +- .../scala/anorm/FunctionAdapterSpec.scala | 62 ++-- core/src/test/scala/anorm/JavaTimeSpec.scala | 3 +- core/src/test/scala/anorm/MacroSpec.scala | 216 ++++++----- core/src/test/scala/anorm/RowSpec.scala | 2 + core/src/test/scala/anorm/SqlResultSpec.scala | 40 ++- .../test/scala/anorm/TupleFlattenerSpec.scala | 132 +++---- .../scalaGuide/main/sql/AnormEnumeratum.md | 2 + .../main/scala/values/ValueEnumColumn.scala | 21 +- .../src/test/scala/EnumColumnSpec.scala | 20 +- .../scala/values/ValueEnumColumnSpec.scala | 7 +- postgres/src/main/scala/package.scala | 15 +- .../test/scala/ParameterMetaDataSpec.scala | 4 +- postgres/src/test/scala/PostgreSQLSpec.scala | 37 +- project/Common.scala | 18 +- project/plugins.sbt | 2 +- .../scala-2/anorm/TokenizedStatement.scala | 5 +- .../scala-3/anorm/TokenizedStatement.scala | 30 +- 41 files changed, 1257 insertions(+), 694 deletions(-) create mode 100644 core/src/main/scala-3/anorm/macros/RowParserImpl.scala create mode 100644 core/src/main/scala/anorm/RowParser.scala diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 83bc17d3..e1f89f4f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -28,7 +28,7 @@ jobs: uses: playframework/.github/.github/workflows/cmd.yml@v2 with: java: 11, 8 - scala: 2.11.12, 2.12.16, 2.13.8, 3.1.3 + scala: 2.11.12, 2.12.16, 2.13.8 # TODO: 3.1.3 (other play modules not ok) cmd: sbt ++$MATRIX_SCALA docs/test tests: @@ -41,7 +41,7 @@ jobs: with: java: 11, 8 scala: 2.11.12, 2.12.16, 2.13.8, 3.1.3 - cmd: sbt ++$MATRIX_SCALA publishLocal test # scapegoat + cmd: sbt ++$MATRIX_SCALA publishLocal test finish: name: Finish diff --git a/.scalafix.conf b/.scalafix.conf index c37b3ef1..55722207 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -12,7 +12,7 @@ OrganizeImports { "re:javax?\\.", "scala.language", "scala.util", "scala.collection", "scala.", - "akka.", + "akka.actor", "akka.stream", "akka.", "play.", "resource.", "acolyte.", diff --git a/.scalafmt.conf b/.scalafmt.conf index b9566093..3f9b9b92 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,5 +1,7 @@ version = 3.5.8 + runner.dialect = Scala213Source3 +project.layout = StandardConvention align.preset = true assumeStandardLibraryStripMargin = true @@ -16,8 +18,6 @@ trailingCommas = preserve newlines.afterCurlyLambda = preserve newlines.beforeCurlyLambdaParams = multilineWithCaseOnly -fileOverride { - "glob:**/src/*/scala-3/**/*.scala" { - runner.dialect = scala3 - } -} \ No newline at end of file +literals.long=Upper +literals.float=Upper +literals.double=Upper \ No newline at end of file diff --git a/akka/src/test/scala/anorm/AkkaStreamSpec.scala b/akka/src/test/scala/anorm/AkkaStreamSpec.scala index ed77f777..ae806279 100644 --- a/akka/src/test/scala/anorm/AkkaStreamSpec.scala +++ b/akka/src/test/scala/anorm/AkkaStreamSpec.scala @@ -7,21 +7,26 @@ import scala.collection.immutable.Seq import scala.concurrent.Future import scala.concurrent.duration._ +import akka.actor.ActorSystem + +import akka.stream.Materializer import akka.stream.scaladsl.{ Keep, Sink, Source } -import acolyte.jdbc.QueryResult import acolyte.jdbc.AcolyteDSL.withQueryResult import acolyte.jdbc.Implicits._ +import acolyte.jdbc.QueryResult import acolyte.jdbc.RowLists.stringList import org.specs2.concurrent.ExecutionEnv final class AkkaStreamSpec(implicit ee: ExecutionEnv) extends org.specs2.mutable.Specification { - "Akka Stream" title + "Akka Stream".title + + implicit lazy val system: ActorSystem = ActorSystem("anorm-tests") - implicit lazy val system = akka.actor.ActorSystem("knox-core-tests") - implicit def materializer = akka.stream.ActorMaterializer.create(system) + implicit def materializer: Materializer = + akka.stream.ActorMaterializer.create(system) // Akka-Contrib issue with Akka-Stream > 2.5.4 // import akka.stream.contrib.TestKit.assertAllStagesStopped @@ -66,8 +71,9 @@ final class AkkaStreamSpec(implicit ee: ExecutionEnv) extends org.specs2.mutable "on success" in assertAllStagesStopped { withQueryResult(stringList :+ "A" :+ "B" :+ "C") { implicit con => - runAsync(Sink.seq[String]) must beLike[ResultSet] { case rs => - (rs.isClosed must beTrue).and(rs.getStatement.isClosed must beTrue).and(con.isClosed must beFalse) + runAsync(Sink.seq[String]) must beLike[ResultSet] { + case rs => + (rs.isClosed must beTrue).and(rs.getStatement.isClosed must beTrue).and(con.isClosed must beFalse) }.await(0, 5.seconds) } } diff --git a/build.sbt b/build.sbt index 23de0150..f6df6e30 100644 --- a/build.sbt +++ b/build.sbt @@ -7,11 +7,10 @@ import com.typesafe.tools.mima.plugin.MimaKeys.{ mimaBinaryIssueFilters, mimaPre // Scalafix inThisBuild( List( - //scalaVersion := "2.13.3", + // scalaVersion := "2.13.3", semanticdbEnabled := true, semanticdbVersion := scalafixSemanticdb.revision, - scalafixDependencies ++= Seq( - "com.github.liancheng" %% "organize-imports" % "0.5.0") + scalafixDependencies ++= Seq("com.github.liancheng" %% "organize-imports" % "0.5.0") ) ) @@ -19,7 +18,8 @@ val specs2Test = Seq( "specs2-core", "specs2-junit", "specs2-matcher-extra" -).map("org.specs2" %% _ % "4.10.6" % Test cross(CrossVersion.for3Use2_13)) +).map("org.specs2" %% _ % "4.10.6" % Test cross (CrossVersion.for3Use2_13)) + .map(_.exclude("org.scala-lang.modules", "*")) lazy val acolyteVersion = "1.2.1" lazy val acolyte = "org.eu.acolyte" %% "jdbc-scala" % acolyteVersion % Test @@ -50,9 +50,8 @@ lazy val `anorm-tokenizer` = project // --- val armShading = Seq( - libraryDependencies += ("com.jsuereth" %% "scala-arm" % "2.1-SNAPSHOT"). - cross(CrossVersion.for3Use2_13), - assembly / test := {}, + libraryDependencies += ("com.jsuereth" %% "scala-arm" % "2.1-SNAPSHOT").cross(CrossVersion.for3Use2_13), + assembly / test := {}, assembly / assemblyOption ~= { _.withIncludeScala(false) // java libraries shouldn't include scala }, @@ -109,6 +108,14 @@ lazy val coreMimaFilter: ProblemFilter = { case _ => true } +lazy val xmlVer = Def.setting[String] { + if (scalaBinaryVersion.value == "2.11") { + "1.3.0" + } else { + "2.1.0" + } +} + lazy val `anorm-core` = project .in(file("core")) .settings( @@ -117,10 +124,19 @@ lazy val `anorm-core` = project (Compile / sourceGenerators) += Def.task { Seq(GFA((Compile / sourceManaged).value / "anorm")) }.taskValue, + scaladocExtractorSkipToken := { + if (scalaBinaryVersion.value == "3") { + "// skip-doc-5f98a5e" + } else { + scaladocExtractorSkipToken.value + } + }, scalacOptions ++= { if (scalaBinaryVersion.value == "3") { Seq.empty - Seq("-Wconf:cat=deprecation&msg=.*(reflectiveSelectableFromLangReflectiveCalls|DeprecatedSqlParser|missing .*ToSql).*:s") + Seq( + "-Wconf:cat=deprecation&msg=.*(reflectiveSelectableFromLangReflectiveCalls|DeprecatedSqlParser|missing .*ToSql).*:s" + ) } else { Seq( "-Xlog-free-terms", @@ -169,6 +185,7 @@ lazy val `anorm-core` = project ProblemFilters.exclude[DirectMissingMethodProblem]( // deprecated 2.3.8 "anorm.SqlQuery.statement" ), + incoRet("anorm.ParameterValue.apply"), // private: ProblemFilters.exclude[DirectMissingMethodProblem]( // private "anorm.Sql.asTry" @@ -183,10 +200,10 @@ lazy val `anorm-core` = project "joda-time" % "joda-time" % "2.11.0", "org.joda" % "joda-convert" % "2.2.2", "org.scala-lang.modules" %% "scala-parser-combinators" % parserCombinatorsVer.value, - "org.scala-lang.modules" %% "scala-xml" % "2.1.0" % Test, - "com.h2database" % "h2" % "2.1.214" % Test, + "org.scala-lang.modules" %% "scala-xml" % xmlVer.value % Test, + "com.h2database" % "h2" % "2.1.214" % Test, acolyte - ) ++ specs2Test.map(_.exclude("org.scala-lang.modules", "*")), + ) ++ specs2Test, ) ++ armShading ) .dependsOn(`anorm-tokenizer`) @@ -194,16 +211,26 @@ lazy val `anorm-core` = project lazy val `anorm-iteratee` = (project in file("iteratee")) .settings( sourceDirectory := { - if (scalaBinaryVersion.value == "2.13") new java.io.File("/no/sources") + val v = scalaBinaryVersion.value + + if (v == "3" || v == "2.13") new java.io.File("/no/sources") else sourceDirectory.value }, mimaPreviousArtifacts := { - if (scalaBinaryVersion.value == "3") Set.empty[ModuleID] + val v = scalaBinaryVersion.value + + if (v == "3" || v == "2.13") Set.empty[ModuleID] else Set(organization.value %% name.value % "2.6.10") }, - publish / skip := { scalaBinaryVersion.value == "2.13" }, + publish / skip := { + val v = scalaBinaryVersion.value + + v == "3" || v == "2.13" + }, libraryDependencies ++= { - if (scalaBinaryVersion.value == "2.13") Seq.empty[ModuleID] + val v = scalaBinaryVersion.value + + if (v == "3" || v == "2.13") Seq.empty[ModuleID] else Seq( "com.typesafe.play" %% "play-iteratees" % "2.6.1", @@ -217,7 +244,10 @@ lazy val `anorm-iteratee` = (project in file("iteratee")) lazy val akkaVer = Def.setting[String] { sys.env.get("AKKA_VERSION").getOrElse { - if (scalaBinaryVersion.value == "2.11") "2.4.20" + val v = scalaBinaryVersion.value + + if (v == "2.11") "2.4.20" + else if (v == "3") "2.6.19" else "2.5.32" } } @@ -237,12 +267,20 @@ lazy val `anorm-akka` = (project in file("akka")) } }, libraryDependencies ++= Seq("akka-testkit", "akka-stream").map { m => - "com.typesafe.akka" %% m % akkaVer.value % Provided + ("com.typesafe.akka" %% m % akkaVer.value % Provided).exclude("org.scala-lang.modules", "*") }, libraryDependencies ++= (acolyte +: specs2Test) ++ Seq( - "com.typesafe.akka" %% "akka-stream-contrib" % akkaContribVer.value % Test + ("com.typesafe.akka" %% "akka-stream-contrib" % akkaContribVer.value % Test) + .cross(CrossVersion.for3Use2_13) + .exclude("com.typesafe.akka", "*") ), - scalacOptions += "-P:silencer:globalFilters=deprecated", + scalacOptions ++= { + if (scalaBinaryVersion.value == "3") { + Seq("-Wconf:cat=deprecation&msg=.*(onDownstreamFinish|ActorMaterializer).*:s") + } else { + Seq("-P:silencer:globalFilters=deprecated") + } + }, Test / unmanagedSourceDirectories ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, n)) if n < 13 => @@ -261,7 +299,7 @@ lazy val `anorm-akka` = (project in file("akka")) lazy val pgVer = sys.env.get("POSTGRES_VERSION").getOrElse("42.4.2") val playVer = Def.setting[String] { - if (scalaVersion.value.startsWith("2.13")) "2.7.3" + if (scalaBinaryVersion.value == "2.13") "2.7.3" else "2.6.14" } @@ -269,8 +307,11 @@ lazy val `anorm-postgres` = (project in file("postgres")) .settings( mimaPreviousArtifacts := Set.empty, libraryDependencies ++= { + val v = scalaBinaryVersion.value + val playJsonVer = { - if (scalaBinaryVersion.value == "2.13") "2.9.2" + if (v == "2.13") "2.9.2" + else if (v == "3") "2.10.0-RC6" else "2.6.7" } @@ -284,8 +325,23 @@ lazy val `anorm-postgres` = (project in file("postgres")) lazy val `anorm-enumeratum` = (project in file("enumeratum")) .settings( + sourceDirectory := { + if (scalaBinaryVersion.value == "3") new java.io.File("/no/sources") + else sourceDirectory.value + }, + publish / skip := { scalaBinaryVersion.value == "3" }, mimaPreviousArtifacts := Set.empty, - libraryDependencies ++= Seq("com.beachape" %% "enumeratum" % "1.7.0", acolyte) ++ specs2Test + libraryDependencies ++= { + if (scalaBinaryVersion.value != "3") { + Seq( + "org.scala-lang.modules" %% "scala-xml" % xmlVer.value % Test, + "com.beachape" %% "enumeratum" % "1.7.0", + acolyte + ) ++ specs2Test + } else { + Seq.empty + } + } ) .dependsOn(`anorm-core`) @@ -320,7 +376,8 @@ ThisBuild / playBuildRepoName := "anorm" addCommandAlias( "validateCode", List( + "scalafixAll -check", "scalafmtSbtCheck", - "scalafmtCheckAll", + "+scalafmtCheckAll" ).mkString(";") ) diff --git a/core/src/main/scala-2/anorm/Macro.scala b/core/src/main/scala-2/anorm/Macro.scala index 4ceabc3a..1fd95b8e 100644 --- a/core/src/main/scala-2/anorm/Macro.scala +++ b/core/src/main/scala-2/anorm/Macro.scala @@ -66,7 +66,10 @@ object Macro extends MacroOptions { def psz = params.size if (names.size < psz) { - c.abort(c.enclosingPosition, s"no column name for parameters: ${show(names)} < $params") + c.abort( + c.enclosingPosition, + s"no column name for parameters: ${names.map(n => show(n)).mkString(", ")} < ${params.map(_.name).mkString(", ")}" + ) } else { parserImpl[T](c) { (t, _, i) => diff --git a/core/src/main/scala-2/anorm/macros/SealedRowParserImpl.scala b/core/src/main/scala-2/anorm/macros/SealedRowParserImpl.scala index 6f5ab01e..eaaae489 100644 --- a/core/src/main/scala-2/anorm/macros/SealedRowParserImpl.scala +++ b/core/src/main/scala-2/anorm/macros/SealedRowParserImpl.scala @@ -57,12 +57,8 @@ private[anorm] object SealedRowParserImpl { val caseName = TermName(c.freshName("discriminated")) val key = q"$discriminate(${subclass.typeSymbol.fullName})" val caseDecl = q"val $caseName = $key" - val subtype = { // TODO: typeParams is not supported anyway - if (subclass.typeSymbol.asClass.typeParams.isEmpty) subclass - else subclass.erasure - } - (key, caseDecl, cq"`$caseName` => implicitly[anorm.RowParser[$subtype]]") + (key, caseDecl, cq"`$caseName` => implicitly[anorm.RowParser[$subclass]]") } lazy val supported = q"List(..${cases.map(_._1)})" diff --git a/core/src/main/scala-2/anorm/macros/ToParameterListImpl.scala b/core/src/main/scala-2/anorm/macros/ToParameterListImpl.scala index cf772ebf..ba92f526 100644 --- a/core/src/main/scala-2/anorm/macros/ToParameterListImpl.scala +++ b/core/src/main/scala-2/anorm/macros/ToParameterListImpl.scala @@ -28,10 +28,9 @@ private[anorm] object ToParameterListImpl { c.inferImplicitValue(ptype) match { case EmptyTree => - c.abort( - c.enclosingPosition, s"Missing ToParameterList[${subcls}]") + c.abort(c.enclosingPosition, s"Missing ToParameterList[${subcls}]") - case toParams => + case toParams => cq"v: ${subcls} => $toParams(v)" } } diff --git a/core/src/main/scala-3/anorm/Macro.scala b/core/src/main/scala-3/anorm/Macro.scala index 98deab51..247e7e24 100644 --- a/core/src/main/scala-3/anorm/Macro.scala +++ b/core/src/main/scala-3/anorm/Macro.scala @@ -31,7 +31,7 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * * case class YourCaseClass(v: Int) * - * val p: RowParser[YourCaseClass] = Macro.namedParser[YourCaseClass] + * //TODO: val p: RowParser[YourCaseClass] = Macro.namedParser[YourCaseClass] * }}} */ inline def namedParser[T]: RowParser[T] = ${ namedParserImpl[T] } @@ -48,10 +48,10 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * * case class YourCaseClass(v: Int) * - * val p: RowParser[YourCaseClass] = Macro.namedParser[YourCaseClass] + * //TODO: val p: RowParser[YourCaseClass] = Macro.namedParser[YourCaseClass] * }}} */ - inline def namedParser[T](naming: ColumnNaming): RowParser[T] = + inline def namedParser[T](inline naming: ColumnNaming): RowParser[T] = ${ namedParserImpl1[T]('naming) } /** @@ -66,11 +66,11 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * * case class YourCaseClass(a: Int, b: String) * - * val p: RowParser[YourCaseClass] = - * Macro.parser[YourCaseClass]("foo", "bar") + * //TODO: val p: RowParser[YourCaseClass] = + * // Macro.parser[YourCaseClass]("foo", "bar") * }}} */ - inline def parser[T](names: String*): RowParser[T] = + inline def parser[T](inline names: String*): RowParser[T] = ${ namedParserImpl3[T]('names) } /** @@ -87,11 +87,11 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * * case class YourCaseClass(a: Int, b: String) * - * val p: RowParser[YourCaseClass] = - * Macro.parser[YourCaseClass]("foo", "loremIpsum") + * // TODO: val p: RowParser[YourCaseClass] = + * // Macro.parser[YourCaseClass]("foo", "loremIpsum") * }}} */ - inline def parser[T](naming: ColumnNaming, names: String*): RowParser[T] = + inline def parser[T](inline naming: ColumnNaming, inline names: String*): RowParser[T] = ${ namedParserImpl2[T]('naming, 'names) } /** @@ -105,7 +105,7 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * * case class YourCaseClass(v: Int) * - * val p: RowParser[YourCaseClass] = Macro.indexedParser[YourCaseClass] + * // TODO: val p: RowParser[YourCaseClass] = Macro.indexedParser[YourCaseClass] * }}} */ inline def indexedParser[T]: RowParser[T] = ${ indexedParserImpl[T] } @@ -122,10 +122,10 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * * case class YourCaseClass(v: Int) * - * val p: RowParser[YourCaseClass] = Macro.offsetParser[YourCaseClass](2) + * //TODO: val p: RowParser[YourCaseClass] = Macro.offsetParser[YourCaseClass](2) * }}} */ - inline def offsetParser[T](offset: Int): RowParser[T] = + inline def offsetParser[T](inline offset: Int): RowParser[T] = ${ offsetParserImpl[T]('offset) } /** @@ -142,7 +142,7 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * @param naming $discriminatorNamingParam * @tparam T $familyTParam */ - inline def sealedParser[T](naming: DiscriminatorNaming): RowParser[T] = + inline def sealedParser[T](inline naming: DiscriminatorNaming): RowParser[T] = ${ sealedParserImpl2[T]('naming) } /** @@ -151,7 +151,7 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * @param discriminate $discriminateParam * @tparam T $familyTParam */ - inline def sealedParser[T](discriminate: Discriminate): RowParser[T] = + inline def sealedParser[T](inline discriminate: Discriminate): RowParser[T] = ${ sealedParserImpl3[T]('discriminate) } /** @@ -161,7 +161,7 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * @param discriminate $discriminateParam * @tparam T $familyTParam */ - inline def sealedParser[T](naming: DiscriminatorNaming, discriminate: Discriminate): RowParser[T] = + inline def sealedParser[T](inline naming: DiscriminatorNaming, discriminate: Discriminate): RowParser[T] = ${ sealedParserImpl[T]('naming, 'discriminate) } // --- @@ -240,7 +240,7 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta * @param projection $projectionParam * @tparam T $caseTParam */ - inline def toParameters[T](inline separator: String, inline projection: ParameterProjection*): ToParameterList[T] = ${ + inline def toParameters[T](inline separator: String, projection: ParameterProjection*): ToParameterList[T] = ${ parametersWithSeparator[T]('separator, 'projection) } @@ -265,21 +265,20 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta // --- - private def namedParserImpl[T](using Quotes, Type[T]): Expr[RowParser[T]] = - withColumn[T] { col => - parserImpl[T] { (n, _) => - '{ anorm.SqlParser.get[T](${ Expr(n) })($col) } - } + private def namedParserImpl[A](using q: Quotes, tpe: Type[A]): Expr[RowParser[A]] = { + parserImpl[A](q) { + [T] => (_: Type[T]) ?=> (col: Expr[Column[T]], n: String, _: Int) => '{ SqlParser.get[T](${ Expr(n) })($col) } } + } - private def namedParserImpl1[T]( + private def namedParserImpl1[A]( naming: Expr[ColumnNaming] - )(using Quotes, Type[T], Type[ColumnNaming]): Expr[RowParser[T]] = - withColumn[T] { col => - parserImpl[T] { (n, _) => - '{ anorm.SqlParser.get[T]($naming(${ Expr(n) }))($col) } - } + )(using q: Quotes, tpe: Type[A], colNme: Type[ColumnNaming]): Expr[RowParser[A]] = { + parserImpl[A](q) { + [T] => + (_: Type[T]) ?=> (col: Expr[Column[T]], n: String, _: Int) => '{ SqlParser.get[T]($naming(${ Expr(n) }))($col) } } + } private def namedParserImpl2[T]( naming: Expr[ColumnNaming], @@ -297,15 +296,16 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta Type[ColumnNaming] ): Expr[RowParser[T]] = namedParserImpl4[T](names)(identity) - private def namedParserImpl4[T]( + private def namedParserImpl4[A]( names: Expr[Seq[String]] - )(naming: Expr[String] => Expr[String])(using q: Quotes, tpe: Type[T]): Expr[RowParser[T]] = { + )(naming: Expr[String] => Expr[String])(using q: Quotes, tpe: Type[A]): Expr[RowParser[A]] = { + import q.reflect.* - val repr = TypeRepr.of[T](using tpe) + val repr = TypeRepr.of[A](using tpe) val ctor = repr.typeSymbol.primaryConstructor - val params = ctor.paramSymss.flatten + val params = ctor.paramSymss.map(_.filterNot(_.isType)).flatten @SuppressWarnings(Array("ListSize")) def psz = params.size @@ -313,31 +313,34 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta val ns = names.valueOrAbort if (ns.size < psz) { - report.errorAndAbort(s"no column name for parameters: ${ns.mkString(", ")} < $params") + report.errorAndAbort(s"no column name for parameters: ${ns.mkString(", ")} < ${params.mkString("[", ", ", "]")}") } else { - parserImpl[T] { (_, i) => - ns.lift(i) match { - case Some(n) => - withColumn[T] { col => - val cn = naming(Expr(n)) - - '{ SqlParser.get[T]($cn)($col) } + parserImpl[A](q) { + [T] => + (_: Type[T]) ?=> + (col: Expr[Column[T]], _: String, i: Int) => + ns.lift(i) match { + case Some(n) => { + val cn = naming(Expr(n)) + + '{ SqlParser.get[T]($cn)($col) } + } + + case _ => + report.errorAndAbort(s"missing column name for parameter $i") } - - case _ => - report.errorAndAbort(s"missing column name for parameter $i") - } } } } - private def offsetParserImpl[T](offset: Expr[Int])(using Quotes, Type[T]): Expr[RowParser[T]] = - withColumn[T] { col => - parserImpl[T] { (_, i) => - '{ anorm.SqlParser.get[T]($offset + ${ Expr(i + 1) })($col) } - } + private def offsetParserImpl[A](offset: Expr[Int])(using q: Quotes, tpe: Type[A]): Expr[RowParser[A]] = { + parserImpl[A](q) { + [T] => + (_: Type[T]) ?=> + (col: Expr[Column[T]], _: String, i: Int) => '{ SqlParser.get[T]($offset + ${ Expr(i + 1) })($col) } } + } private def indexedParserImpl[T](using Quotes, Type[T]): Expr[RowParser[T]] = offsetParserImpl[T]('{ 0 }) @@ -361,9 +364,30 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta ): Expr[RowParser[T]] = macros.SealedRowParserImpl[T](naming, discriminate) - private def parserImpl[T](genGet: (String, Int) => Expr[RowParser[T]])(using Quotes, Type[T]): Expr[RowParser[T]] = { - // TODO: anorm.macros.RowParserImpl[T](c)(genGet) - '{ ??? } + inline private def withParser[T](f: RowParser[T] => (Row => SqlResult[T])): RowParser[T] = new RowParser[T] { self => + lazy val underlying = f(self) + + def apply(row: Row): SqlResult[T] = underlying(row) + } + + /** + * @tparam T the field type + */ + private[anorm] type RowParserGenerator = + [T] => (fieldType: Type[T]) ?=> (column: Expr[Column[T]], fieldName: String, fieldIndex: Int) => Expr[RowParser[T]] + + /** + * @tparam A the case class type + * @param genGet the function applied to each field of case class `A` + */ + private def parserImpl[A](q: Quotes)(genGet: RowParserGenerator)(using Type[A]): Expr[RowParser[A]] = { + given quotes: Quotes = q + + '{ + withParser[A] { self => + ${ macros.RowParserImpl[A](q, 'self)(genGet) } + } + } } // --- @@ -516,7 +540,7 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta } } - inline private[anorm] def withSelfToParameterList[T]( + inline private def withSelfToParameterList[T]( f: ToParameterList[T] => (T => List[NamedParameter]) ): ToParameterList[T] = new ToParameterList[T] { self => lazy val underlying = f(self) diff --git a/core/src/main/scala-3/anorm/macros/RowParserImpl.scala b/core/src/main/scala-3/anorm/macros/RowParserImpl.scala new file mode 100644 index 00000000..9be3422c --- /dev/null +++ b/core/src/main/scala-3/anorm/macros/RowParserImpl.scala @@ -0,0 +1,335 @@ +package anorm.macros + +import scala.quoted.{ Expr, Quotes, Type } + +import anorm.{ Column, Row, RowParser, SqlResult, ~ } +import anorm.Macro.{ RowParserGenerator, debugEnabled } + +private[anorm] object RowParserImpl { + def apply[A]( + q: Quotes, + forwardExpr: Expr[RowParser[A]] + )(genGet: RowParserGenerator)(using tpe: Type[A], parserTpe: Type[RowParser]): Expr[Row => SqlResult[A]] = { + given quotes: Quotes = q + + import q.reflect.* + + val (repr, erased, aTArgs) = TypeRepr.of[A](using tpe) match { + case tpr @ AppliedType(e, args) => + Tuple3( + tpr, + e, + args.collect { + case repr: TypeRepr => + repr + } + ) + + case tpr => + Tuple3(tpr, tpr, List.empty[TypeRepr]) + } + + @inline def abort(msg: String) = report.errorAndAbort(msg) + + val tpeSym = repr.typeSymbol + + if (!tpeSym.isClassDef || !tpeSym.flags.is(Flags.Case)) { + abort(s"case class expected: ${repr.show}") + } + + // --- + + val ctor = tpeSym.primaryConstructor + + val (boundTypes, properties) = ctor.paramSymss match { + case targs :: paramss if targs.forall(_.isType) && paramss.headOption.exists(_.nonEmpty) => { + val boundTps = targs.zip(aTArgs).toMap + + boundTps -> paramss + } + + case params :: Nil if !params.exists(_.isType) => + Map.empty[Symbol, TypeRepr] -> List(params) + + case _ => + report.errorAndAbort(s"${repr.show} constructor has no parameter") + } + + if (properties.isEmpty) { + abort(s"parsed data cannot be passed as parameter: $ctor") + } + + val debug = { + if (debugEnabled) report.info(_: String) + else (_: String) => {} + } + + val resolv = ImplicitResolver[A](q).resolver(forwardExpr, Map.empty, debug)(parserTpe) + + // --- + + /* + * @tparam T the type of parsed data (single column or tuple-like `~`) + * @param parsing the parsing expression (e.g. `get("a") ~ get("b")`) + * @param parsedTpr the representation of type `T` + * @param matchPattern the match pattern to extract values inside `.map` + * @param columnSymss the column symbols bounds in the `matchPattern` (list of list as properties can be passed as multiple parameter list to the constructor) + */ + case class GenerationState[T]( + parsing: Expr[RowParser[T]], + parsedTpr: TypeRepr, + matchPattern: Tree, + columnSymss: List[List[Symbol]] + ) + + val pkg = Symbol.requiredPackage("anorm") + val TildeSelect = (for { + ts <- pkg.declaredType("~").headOption.map(_.companionModule) + un <- ts.declaredMethod("unapply").headOption + } yield Ref(pkg).select(ts).select(un)) match { + case Some(select) => + select + + case _ => + abort("Fails to resolve ~ symbol") + } + + @annotation.tailrec + def prepare[T]( + propss: List[List[Symbol]], + pi: Int, + combined: Option[GenerationState[T]], + hasSelfRef: Boolean, + hasGenericProperty: Boolean + )(using Type[T]): Option[Expr[Row => SqlResult[A]]] = + propss.headOption match { + case Some(sym :: localTail) => { + val tn = sym.name + + val tt: TypeRepr = sym.tree match { + case vd: ValDef => { + val vtpe = vd.tpt.tpe + + boundTypes.getOrElse(vtpe.typeSymbol, vtpe) + } + + case _ => + abort(s"Value definition expected for ${repr.show} constructor parameter: $sym") + } + + val isGenericProp = tt match { + case AppliedType(_, as) => + as.nonEmpty + + case _ => + false + } + + val colSym: Symbol = + Symbol.newBind(Symbol.spliceOwner, tn, Flags.Case, tt) + + // Pattern to match a single column in `.map` pattern matching + val singlePat = Bind(colSym, Typed(Wildcard(), Inferred(tt))) + + tt.asType match { + case '[t] => + def initialState(expr: Expr[RowParser[t]]) = + GenerationState[t]( + parsing = expr, + parsedTpr = tt, + matchPattern = singlePat, + columnSymss = List(colSym) :: Nil + ) + + def combineState( + parent: GenerationState[T], + expr: Expr[RowParser[T ~ t]] + ): GenerationState[T ~ t] = { + val pat = Unapply( + fun = TypeApply(TildeSelect, List(Inferred(parent.parsedTpr), Inferred(tt))), + implicits = Nil, + patterns = List(parent.matchPattern, singlePat) + ) + + GenerationState[T ~ t]( + parsing = expr, + parsedTpr = TypeRepr.of[T ~ t], + matchPattern = pat, + columnSymss = parent.columnSymss match { + case headList :: tails => + (colSym :: headList) :: tails + + case _ => { + // Should have been handled by initialState, smth wrong + abort(s"Fails to handle initial state of RowParser generation: ${repr.show}") + } + } + ) + } + + Expr.summon[Column[t]] match { + case None => + // ... try to resolve `RowParser[tt]` + resolv(tt) match { + case None => + abort(s"cannot find Column nor RowParser for ${tn}:${tt.show} in ${ctor.fullName}") + + case Some((pr, s)) => { + val hasSelf = if s then s else hasSelfRef + + // Use an existing `RowParser[t]` as part + val tpr: Expr[RowParser[t]] = pr.asExprOf[RowParser[t]] + + combined match { + case Some(parent @ GenerationState(prev, _, _, _)) => + prepare[T ~ t]( + propss = localTail :: propss.tail, + pi = pi + 1, + combined = Some { + combineState(parent, '{ $prev ~ $tpr }) + }, + hasSelfRef = hasSelf, + hasGenericProperty = isGenericProp || hasGenericProperty + ) + + case _ => + prepare[t]( + propss = localTail :: propss.tail, + pi = pi + 1, + combined = Some(initialState(tpr)), + hasSelfRef = hasSelf, + hasGenericProperty = isGenericProp || hasGenericProperty + ) + } + } + } + + case Some(col) => { + // Generate a `get` for the `Column[T]` + val get: Expr[RowParser[t]] = genGet[t](col, tn, pi) + + combined match { + case Some(parent @ GenerationState(prev, _, _, _)) => + prepare[T ~ t]( + propss = localTail :: propss.tail, + pi = pi + 1, + combined = Some { + combineState(parent, '{ $prev ~ $get }) + }, + hasSelfRef = hasSelfRef, + hasGenericProperty = isGenericProp || hasGenericProperty + ) + + case None => + prepare[t]( + propss = localTail :: propss.tail, + pi = pi + 1, + combined = Some(initialState(get)), + hasSelfRef = hasSelfRef, + hasGenericProperty = isGenericProp || hasGenericProperty + ) + } + } + } + } + } + + case Some(Nil) if propss.tail.nonEmpty => { + // End of one parameter list for the properties, but there is more + + prepare[T]( + propss = propss.tail, // other parameter lists + pi = pi, + combined = combined match { + case Some(state) => + Some(state.copy(columnSymss = Nil :: state.columnSymss)) + + case None => + abort("Missing generation state: ${repr.show}") + }, + hasSelfRef = hasSelfRef, + hasGenericProperty = hasGenericProperty + ) + } + + case Some(Nil) | None => + combined match { + case None => + None + + case Some(GenerationState(parsing, _, matchPattern, revColss)) => { + val targs = boundTypes.values.toList + val colArgss = revColss.reverse.map { + _.reverse.map(Ref(_: Symbol)) + } + val newTerm = New(Inferred(erased)).select(ctor) + + val ctorCall: Expr[A] = { + if (targs.nonEmpty) { + newTerm.appliedToTypes(targs).appliedToArgss(colArgss) + } else { + newTerm.appliedToArgss(colArgss) + } + }.asExprOf[A] + + val ctorCase = CaseDef( + pattern = matchPattern, + guard = None, + rhs = '{ anorm.Success[A](${ ctorCall }) }.asTerm + ) + + inline def cases[U: Type](inline parsed: Expr[U]) = { + // Workaround as in case of generic property + // (whose type has type arguments), false warning is raised + // about exhaustivity. + + if (hasGenericProperty) { + List( + ctorCase, + CaseDef( + Wildcard(), + guard = None, + rhs = '{ + anorm.Error( + anorm.SqlMappingError( + "Unexpected parsed value: " + ${ parsed } + ) + ) + }.asTerm + ) + ) + } else { + List(ctorCase) + } + } + + inline def flatMapParsed[U: Type](inline parsed: Expr[U]): Expr[SqlResult[A]] = + Match(parsed.asTerm, cases(parsed)).asExprOf[SqlResult[A]] + + Some('{ + lazy val parsingRow = ${ parsing } + + { (row: Row) => + parsingRow(row).flatMap { parsed => + ${ flatMapParsed('parsed) } + } + } + }) + } + } + } + + val generated: Expr[Row => SqlResult[A]] = + prepare[Nothing](properties, 0, None, false, false) match { + case Some(fn) => + fn + + case _ => + abort(s"Fails to prepare the parser function: ${repr.show}") + } + + debug(s"row parser generated for ${repr.show}: ${generated.show}") + + generated + } +} diff --git a/core/src/main/scala-3/anorm/macros/SealedRowParserImpl.scala b/core/src/main/scala-3/anorm/macros/SealedRowParserImpl.scala index e1c21485..5b0e6784 100644 --- a/core/src/main/scala-3/anorm/macros/SealedRowParserImpl.scala +++ b/core/src/main/scala-3/anorm/macros/SealedRowParserImpl.scala @@ -7,8 +7,8 @@ import anorm.{ Error, RowParser, SqlMappingError, SqlParser } private[anorm] object SealedRowParserImpl { def apply[A]( - naming: Expr[DiscriminatorNaming], - discriminate: Expr[Discriminate] + naming: Expr[DiscriminatorNaming], + discriminate: Expr[Discriminate] )(using q: Quotes, tpe: Type[A]): Expr[RowParser[A]] = { import q.reflect.* @@ -48,26 +48,26 @@ private[anorm] object SealedRowParserImpl { } if (missing.nonEmpty) { - def details = missing.map { subcls => + def details = missing + .map { subcls => s"- cannot find anorm.RowParser[${subcls.show}] in the implicit scope" } .mkString(",\r\n") - report.errorAndAbort( - s"fails to generate sealed parser: ${repr.show};\r\n$details") + report.errorAndAbort(s"fails to generate sealed parser: ${repr.show};\r\n$details") } // --- - val cases: List[(String, CaseDef)] = subParsers.result().map { + val cases: List[(String, CaseDef)] = subParsers.result().map { case (subcls, subParser) => val tpeSym = subcls.typeSymbol val tpeName = { - if (tpeSym.flags is Flags.Module) tpeSym.fullName.stripSuffix(f"$$") + if (tpeSym.flags.is(Flags.Module)) tpeSym.fullName.stripSuffix(f"$$") else tpeSym.fullName } - val key = '{ $discriminate(${Expr(tpeName)}) } + val key = '{ $discriminate(${ Expr(tpeName) }) } val bind = Symbol.newBind( @@ -82,7 +82,8 @@ private[anorm] object SealedRowParserImpl { tpeSym.fullName -> CaseDef( Bind(bind, Wildcard()), guard = Some('{ $ref == $key }.asTerm), - rhs = subParser.asTerm) + rhs = subParser.asTerm + ) } def fallbackCase: CaseDef = { @@ -100,8 +101,8 @@ private[anorm] object SealedRowParserImpl { Bind(fallbackBind, Wildcard()), guard = None, rhs = '{ - val msg = "unexpected row type '%s'; expected: %s". - format($fallbackVal, ${Expr(cases.map(_._1))}.mkString(", ")) + val msg = + "unexpected row type '%s'; expected: %s".format($fallbackVal, ${ Expr(cases.map(_._1)) }.mkString(", ")) RowParser.failed[A](Error(SqlMappingError(msg))) }.asTerm @@ -115,7 +116,7 @@ private[anorm] object SealedRowParserImpl { ).asExprOf[RowParser[A]] val parser: Expr[RowParser[A]] = '{ - val discriminatorCol = $naming(${Expr(repr.typeSymbol.fullName)}) + val discriminatorCol = $naming(${ Expr(repr.typeSymbol.fullName) }) SqlParser.str(discriminatorCol).flatMap { (discriminatorVal: String) => ${ body('discriminatorVal) } diff --git a/core/src/main/scala-3/anorm/macros/ToParameterListImpl.scala b/core/src/main/scala-3/anorm/macros/ToParameterListImpl.scala index 621827cc..a1be8b06 100644 --- a/core/src/main/scala-3/anorm/macros/ToParameterListImpl.scala +++ b/core/src/main/scala-3/anorm/macros/ToParameterListImpl.scala @@ -92,47 +92,45 @@ private[anorm] object ToParameterListImpl { @inline def abort(msg: String) = report.errorAndAbort(msg) - val debug = { - if (debugEnabled) report.info(_: String) - else (_: String) => {} - } - if (!tpeSym.isClassDef || !tpeSym.flags.is(Flags.Case)) { abort(s"Case class expected: $tpe") } val ctor = tpeSym.primaryConstructor - if (ctor.paramSymss.isEmpty) { - abort("parsed data cannot be passed as constructor parameters") - } - - val resolv = ImplicitResolver[A](q).resolver(forwardExpr, Map.empty, debug)(tsTpe) - - // --- - val (boundTypes, properties) = ctor.paramSymss match { - case targs :: params :: Nil if targs.forall(_.isType) => { + case targs :: params :: tail if targs.forall(_.isType) => { + if (tail.nonEmpty) { + report.info( + s"${aTpr.show} constructor has multiple list of parameters. As for unapply, only for the first one will be considered" + ) + } + val boundTps = targs.zip(aTArgs).toMap boundTps -> params } - case params :: Nil => - Map.empty[Symbol, TypeRepr] -> params - - case params :: _ => { - report.info( - s"${aTpr.show} constructor has multiple list of parameters. As for unapply, only for the first one will be considered" - ) - + case params :: Nil if !params.exists(_.isType) => Map.empty[Symbol, TypeRepr] -> params - } case _ => report.errorAndAbort(s"${aTpr.show} constructor has no parameter") } + if (properties.isEmpty) { + abort("parsed data cannot be passed as constructor parameters") + } + + // --- + + val debug = { + if (debugEnabled) report.info(_: String) + else (_: String) => {} + } + + val resolv = ImplicitResolver[A](q).resolver(forwardExpr, Map.empty, debug)(tsTpe) + val compiledProjection: Seq[ParameterProjection] = { import _root_.anorm.Macro.parameterProjectionFromExpr diff --git a/core/src/main/scala/anorm/Anorm.scala b/core/src/main/scala/anorm/Anorm.scala index c329bb89..3c35242b 100644 --- a/core/src/main/scala/anorm/Anorm.scala +++ b/core/src/main/scala/anorm/Anorm.scala @@ -17,7 +17,7 @@ import resource.{ managed, ManagedResource, Resource } * {{{ * import anorm._ * - * def foo(v: Any) = SQL("UPDATE t SET val = {o}").on('o -> anorm.Object(v)) + * def foo(v: Any) = SQL("UPDATE t SET val = {o}").on("o" -> anorm.Object(v)) * }}} */ case class Object(value: Any) @@ -277,7 +277,6 @@ object Sql { // TODO: Rename to SQL private def toSql(ts: List[StatementToken], buf: StringBuilder): StringBuilder = ts.foldLeft(buf) { case (sql, StringToken(t)) => sql ++= t case (sql, PercentToken) => sql += '%' - // TODO: Remove; case (sql, _) => sql } @SuppressWarnings(Array("IncorrectlyNamedExceptions")) diff --git a/core/src/main/scala/anorm/Column.scala b/core/src/main/scala/anorm/Column.scala index 5d22633c..76f472de 100644 --- a/core/src/main/scala/anorm/Column.scala +++ b/core/src/main/scala/anorm/Column.scala @@ -13,11 +13,11 @@ import java.net.{ URI, URL } import java.sql.Timestamp -import scala.reflect.ClassTag - import scala.util.{ Failure, Success => TrySuccess, Try } import scala.util.control.NonFatal +import scala.reflect.ClassTag + import resource.managed /** @@ -653,7 +653,7 @@ sealed trait JodaColumn { * * {{{ * import org.joda.time.LocalDate - * import anorm.{ SQL, SqlParser }, SqlParser.scalar + * import anorm._, SqlParser.scalar * * def ld(implicit con: java.sql.Connection): LocalDate = * SQL("SELECT last_mod FROM tbl").as(scalar[LocalDate].single) @@ -738,7 +738,7 @@ sealed trait JodaColumn { * Parses column as joda Instant * * {{{ - * import anorm.{ SQL, SqlParser }, SqlParser.scalar + * import anorm._, SqlParser.scalar * import org.joda.time.Instant * * def d(implicit con: java.sql.Connection): Instant = diff --git a/core/src/main/scala/anorm/NamedParameter.scala b/core/src/main/scala/anorm/NamedParameter.scala index defb8906..9995dc75 100644 --- a/core/src/main/scala/anorm/NamedParameter.scala +++ b/core/src/main/scala/anorm/NamedParameter.scala @@ -42,25 +42,15 @@ object NamedParameter { /** * $namedWithSymbol - * - * {{{ - * import anorm.{ NamedParameter, ParameterValue } - * - * def foo(pv: ParameterValue): NamedParameter = 'name -> pv - * }}} */ + @deprecated(message = "Symbol is deprecated", since = "2.7.0") implicit def namedWithSymbol(t: (Symbol, ParameterValue)): NamedParameter = NamedParameter(t._1.name, t._2) /** * $namedWithSymbol - * - * {{{ - * import anorm.NamedParameter - * - * val p: NamedParameter = 'name -> 1L - * }}} */ + @deprecated(message = "Symbol is deprecated", since = "2.7.0") implicit def namedWithSymbol[V](t: (Symbol, V))(implicit c: ToParameterValue[V]): NamedParameter = NamedParameter(t._1.name, c(t._2)) diff --git a/core/src/main/scala/anorm/Row.scala b/core/src/main/scala/anorm/Row.scala index d143690b..da977da4 100644 --- a/core/src/main/scala/anorm/Row.scala +++ b/core/src/main/scala/anorm/Row.scala @@ -77,8 +77,7 @@ trait Row { * @param c Column mapping * * {{{ - * import anorm.SQL - * import anorm.Column.columnToString // mapping column to string + * import anorm._, Column.columnToString // mapping column to string * * val res = SQL("SELECT * FROM Test").map { row => * // string columns 'code' and 'label' @@ -96,8 +95,7 @@ trait Row { * @param c Column mapping * * {{{ - * import anorm.SQL - * import anorm.Column.columnToString // mapping column to string + * import anorm._, Column.columnToString // mapping column to string * * val res = SQL("SELECT * FROM Test").map { row => * row(1) -> row(2) // string columns #1 and #2 diff --git a/core/src/main/scala/anorm/RowParser.scala b/core/src/main/scala/anorm/RowParser.scala new file mode 100644 index 00000000..1da70904 --- /dev/null +++ b/core/src/main/scala/anorm/RowParser.scala @@ -0,0 +1,180 @@ +package anorm + +trait RowParser[+A] extends (Row => SqlResult[A]) { parent => + + /** + * Returns a parser that will apply given function `f` + * to the result of this first parser. If the current parser is not + * successful, the new one will return encountered [[Error]]. + * + * @param f Function applied on the successful parser result + * + * {{{ + * import anorm.{ RowParser, SqlParser } + * + * val parser: RowParser[Int] = SqlParser.str("col").map(_.length) + * // Prepares a parser that first get 'col' string value, + * // and then returns the length of that + * }}} + */ + def map[B](f: A => B): RowParser[B] = RowParser(parent.andThen(_.map(f))) + + /** + * Returns parser which collects information + * from already parsed row data using `f`. + * + * @param otherwise Message returned as error if nothing can be collected using `f`. + * @param f Collecting function + */ + def collect[B](otherwise: String)(f: PartialFunction[A, B]): RowParser[B] = + RowParser(parent(_).flatMap(f.lift(_).fold[SqlResult[B]](Error(SqlMappingError(otherwise)))(Success(_)))) + + def flatMap[B](k: A => RowParser[B]): RowParser[B] = + RowParser(row => parent(row).flatMap(k(_)(row))) + + /** + * Combines this parser on the left of the parser `p` given as argument. + * + * @param p Parser on the right + * + * {{{ + * import anorm._, SqlParser.{ int, str } + * + * def populations(implicit con: java.sql.Connection): List[String ~ Int] = + * SQL("SELECT * FROM Country").as((str("name") ~ int("population")).*) + * }}} + */ + def ~[B](p: RowParser[B]): RowParser[A ~ B] = + RowParser(row => parent(row).flatMap(a => p(row).map(new ~(a, _)))) + + /** + * Combines this current parser with the one given as argument `p`, + * if and only if the current parser can first/on left side successfully + * parse a row, without keeping these values in parsed result. + * + * {{{ + * import anorm._, SqlParser.{ int, str } + * + * def string(implicit con: java.sql.Connection) = SQL("SELECT * FROM test"). + * as((int("id") ~> str("val")).single) + * // row has to have an int column 'id' and a string 'val' one, + * // keeping only 'val' in result + * }}} + */ + def ~>[B](p: RowParser[B]): RowParser[B] = + RowParser(row => parent(row).flatMap(_ => p(row))) + + /** + * Combines this current parser with the one given as argument `p`, + * if and only if the current parser can first successfully + * parse a row, without keeping the values of the parser `p`. + * + * {{{ + * import anorm._, SqlParser.{ int, str } + * + * def i(implicit con: java.sql.Connection) = SQL("SELECT * FROM test"). + * as((int("id") <~ str("val")).single) + * // row has to have an int column 'id' and a string 'val' one, + * // keeping only 'id' in result + * }}} + */ + def <~[B](p: RowParser[B]): RowParser[A] = parent.~(p).map(_._1) + + // TODO: Scaladoc + def |[B >: A](p: RowParser[B]): RowParser[B] = RowParser { row => + parent(row) match { + case Error(_) => p(row) + case a => a + } + } + + /** + * Returns a row parser for optional column, + * that will turn missing or null column as None. + */ + def ? : RowParser[Option[A]] = RowParser { + parent(_) match { + case Success(a) => Success(Some(a)) + case Error(ColumnNotFound(_, _)) => + Success(None) + + case e @ Error(_) => e + } + } + + /** Alias for [[flatMap]] */ + def >>[B](f: A => RowParser[B]): RowParser[B] = flatMap(f) + + /** + * Returns possibly empty list parsed from result. + * + * {{{ + * import anorm._, SqlParser.scalar + * + * val price = 125 + * + * def foo(implicit con: java.sql.Connection) = + * SQL"SELECT name FROM item WHERE price < \\$price".as(scalar[String].*) + * }}} + */ + def * : ResultSetParser[List[A]] = ResultSetParser.list(parent) + + /** + * Returns non empty list parse from result, + * or raise error if there is no result. + * + * {{{ + * import anorm._, SqlParser.str + * + * def foo(implicit con: java.sql.Connection) = { + * val parser = str("title") ~ str("descr") + * SQL("SELECT title, descr FROM pages").as(parser.+) // at least 1 page + * } + * }}} + */ + def + : ResultSetParser[List[A]] = ResultSetParser.nonEmptyList(parent) + + /** + * Returns a result set parser expecting exactly one row to parse. + * + * {{{ + * import anorm._, SqlParser.scalar + * + * def b(implicit con: java.sql.Connection): Boolean = + * SQL("SELECT flag FROM Test WHERE id = :id"). + * on("id" -> 1).as(scalar[Boolean].single) + * }}} + * + * @see #singleOpt + */ + def single = ResultSetParser.single(parent) + + /** + * Returns a result set parser for none or one parsed row. + * + * {{{ + * import anorm._, SqlParser.scalar + * + * def name(implicit con: java.sql.Connection): Option[String] = + * SQL("SELECT name FROM Country WHERE lang = :lang") + * .on("lang" -> "notFound").as(scalar[String].singleOpt) + * }}} + */ + def singleOpt: ResultSetParser[Option[A]] = ResultSetParser.singleOpt(parent) + +} + +object RowParser { + def apply[A](f: Row => SqlResult[A]): RowParser[A] = new RowParser[A] { + def apply(row: Row): SqlResult[A] = f(row) + } + + /** Row parser that result in successfully unchanged row. */ + object successful extends RowParser[Row] { + def apply(row: Row): SqlResult[Row] = Success(row) + } + + def failed[A](error: => Error): RowParser[A] = new RowParser[A] { + def apply(row: Row): SqlResult[A] = error + } +} diff --git a/core/src/main/scala/anorm/SimpleSql.scala b/core/src/main/scala/anorm/SimpleSql.scala index b1178996..fc462788 100644 --- a/core/src/main/scala/anorm/SimpleSql.scala +++ b/core/src/main/scala/anorm/SimpleSql.scala @@ -92,16 +92,7 @@ case class SimpleSql[T]( stmt } - /** - * Prepares query with given row parser. - * - * {{{ - * import anorm.{ SQL, SqlParser } - * - * val res = SQL("SELECT 1").using(SqlParser.scalar[Int]) - * // See: SQL("SELECT 1").as(SqlParser.scalar[Int].single) - * }}} - */ + /** Prepares query with given row parser. */ @deprecated(message = "Use [[as]]", since = "2.5.1") def using[U](p: RowParser[U]): SimpleSql[U] = copy(sql, params, p) diff --git a/core/src/main/scala/anorm/SqlParser.scala b/core/src/main/scala/anorm/SqlParser.scala index dcc65690..1b5d42e4 100644 --- a/core/src/main/scala/anorm/SqlParser.scala +++ b/core/src/main/scala/anorm/SqlParser.scala @@ -13,7 +13,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Returns a parser for a scalar not-null value. * * {{{ - * import anorm.{ SQL, SqlParser }, SqlParser.scalar + * import anorm._, SqlParser.scalar * * def count(implicit con: java.sql.Connection) = * SQL("select count(*) from Country").as(scalar[Long].single) @@ -40,7 +40,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Returns a parser that fold over the row. * * {{{ - * import anorm.{ RowParser, SqlParser } + * import anorm._ * * def p(implicit con: java.sql.Connection): RowParser[List[(Any, String)]] = * SqlParser.folder(List.empty[(Any, String)]) { (ls, v, m) => @@ -67,7 +67,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Flatten columns tuple-like. * * {{{ - * import anorm.{ SQL, SqlParser }, SqlParser.{ long, str, int } + * import anorm._, SqlParser.{ long, str, int } * * def tuple(implicit con: java.sql.Connection): (Long, String, Int) = * SQL("SELECT a, b, c FROM Test"). @@ -110,7 +110,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as float. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Float, String) = * SQL("SELECT a, b FROM test").as( @@ -126,7 +126,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Float, String) = * SQL("SELECT a, b FROM test").as( @@ -141,7 +141,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as string. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Float, String) = * SQL("SELECT a, b FROM test").as( @@ -157,7 +157,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Float, String) = * SQL("SELECT a, b FROM test").as( @@ -173,7 +173,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * * {{{ * import java.io.InputStream - * import anorm.{ SQL, SqlParser } + * import anorm._ * * val parser = (SqlParser.str("name") ~ SqlParser.binaryStream("data")). * map(SqlParser.flatten) @@ -190,7 +190,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * * {{{ * import java.io.InputStream - * import anorm.{ SQL, SqlParser } + * import anorm._ * * val parser = * (SqlParser.str(1) ~ SqlParser.binaryStream(2)).map(SqlParser.flatten) @@ -206,7 +206,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as boolean. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Boolean, String) = * SQL("SELECT a, b FROM test").as( @@ -221,7 +221,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Boolean, String) = * SQL("SELECT a, b FROM test").as( @@ -235,7 +235,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as byte. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Byte, String) = * SQL("SELECT a, b FROM test").as( @@ -251,7 +251,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Byte, String) = * SQL("SELECT a, b FROM test").as( @@ -266,7 +266,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as binary stream. * * {{{ - * import anorm.{ SQL, SqlParser }, SqlParser.{ str, byteArray } + * import anorm._, SqlParser.{ str, byteArray } * * val parser = (str("name") ~ byteArray("data")).map(SqlParser.flatten) * @@ -281,7 +281,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as binary stream. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * val parser = * (SqlParser.str(1) ~ SqlParser.byteArray(2)).map(SqlParser.flatten) @@ -297,7 +297,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as double. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Double, String) = * SQL("SELECT a, b FROM test").as( @@ -312,7 +312,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Double, String) = * SQL("SELECT a, b FROM test").as( @@ -326,7 +326,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as short. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Short, String) = * SQL("SELECT a, b FROM test").as( @@ -342,7 +342,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Short, String) = * SQL("SELECT a, b FROM test").as( @@ -357,7 +357,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as integer. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Int, String) = * SQL("SELECT a, b FROM test").as( @@ -373,7 +373,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Int, String) = * SQL("SELECT a, b FROM test") @@ -388,7 +388,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified array column as list. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (String, List[String]) = * SQL("SELECT a, sqlArrayOfString FROM test") @@ -403,7 +403,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (String, List[String]) = * SQL("SELECT a, sqlArrayOfString FROM test") @@ -417,7 +417,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as long. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Long, String) = * SQL("SELECT a, b FROM test").as( @@ -433,7 +433,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (Long, String) = * SQL("SELECT a, b FROM test").as( @@ -448,7 +448,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * Parses specified column as date. * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (java.util.Date, String) = * SQL("SELECT a, b FROM test").as( @@ -464,7 +464,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param columnPosition from 1 to n * * {{{ - * import anorm.{ SQL, SqlParser } + * import anorm._ * * def t(implicit con: java.sql.Connection): (java.util.Date, String) = * SQL("SELECT a, b FROM test").as( @@ -480,8 +480,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * @param name Column name * * {{{ - * import anorm.SQL - * import anorm.SqlParser.get + * import anorm._, SqlParser.get * * def title(implicit con: java.sql.Connection): String = * SQL("SELECT title FROM Books").as(get[String]("title").single) @@ -524,8 +523,7 @@ object SqlParser extends FunctionAdapter with DeprecatedSqlParser { * and matching expected `value`. * * {{{ - * import anorm.SQL - * import anorm.SqlParser.matches + * import anorm._, SqlParser.matches * * def m(implicit con: java.sql.Connection): Boolean = * SQL("SELECT * FROM table").as(matches("a", 1.2f).single) @@ -567,185 +565,6 @@ sealed trait DeprecatedSqlParser { _parser: SqlParser.type => @SuppressWarnings(Array("ClassNames")) final case class ~[+A, +B](_1: A, _2: B) -object RowParser { - def apply[A](f: Row => SqlResult[A]): RowParser[A] = new RowParser[A] { - def apply(row: Row): SqlResult[A] = f(row) - } - - /** Row parser that result in successfully unchanged row. */ - object successful extends RowParser[Row] { - def apply(row: Row): SqlResult[Row] = Success(row) - } - - def failed[A](error: => Error): RowParser[A] = new RowParser[A] { - def apply(row: Row): SqlResult[A] = error - } -} - -trait RowParser[+A] extends (Row => SqlResult[A]) { parent => - - /** - * Returns a parser that will apply given function `f` - * to the result of this first parser. If the current parser is not - * successful, the new one will return encountered [[Error]]. - * - * @param f Function applied on the successful parser result - * - * {{{ - * import anorm.{ RowParser, SqlParser } - * - * val parser: RowParser[Int] = SqlParser.str("col").map(_.length) - * // Prepares a parser that first get 'col' string value, - * // and then returns the length of that - * }}} - */ - def map[B](f: A => B): RowParser[B] = RowParser(parent.andThen(_.map(f))) - - /** - * Returns parser which collects information - * from already parsed row data using `f`. - * - * @param otherwise Message returned as error if nothing can be collected using `f`. - * @param f Collecting function - */ - def collect[B](otherwise: String)(f: PartialFunction[A, B]): RowParser[B] = - RowParser(parent(_).flatMap(f.lift(_).fold[SqlResult[B]](Error(SqlMappingError(otherwise)))(Success(_)))) - - def flatMap[B](k: A => RowParser[B]): RowParser[B] = - RowParser(row => parent(row).flatMap(k(_)(row))) - - /** - * Combines this parser on the left of the parser `p` given as argument. - * - * @param p Parser on the right - * - * {{{ - * import anorm._, SqlParser.{ int, str } - * - * def populations(implicit con: java.sql.Connection): List[String ~ Int] = - * SQL("SELECT * FROM Country").as((str("name") ~ int("population")).*) - * }}} - */ - def ~[B](p: RowParser[B]): RowParser[A ~ B] = - RowParser(row => parent(row).flatMap(a => p(row).map(new ~(a, _)))) - - /** - * Combines this current parser with the one given as argument `p`, - * if and only if the current parser can first/on left side successfully - * parse a row, without keeping these values in parsed result. - * - * {{{ - * import anorm._, SqlParser.{ int, str } - * - * def string(implicit con: java.sql.Connection) = SQL("SELECT * FROM test"). - * as((int("id") ~> str("val")).single) - * // row has to have an int column 'id' and a string 'val' one, - * // keeping only 'val' in result - * }}} - */ - def ~>[B](p: RowParser[B]): RowParser[B] = - RowParser(row => parent(row).flatMap(_ => p(row))) - - /** - * Combines this current parser with the one given as argument `p`, - * if and only if the current parser can first successfully - * parse a row, without keeping the values of the parser `p`. - * - * {{{ - * import anorm._, SqlParser.{ int, str } - * - * def i(implicit con: java.sql.Connection) = SQL("SELECT * FROM test"). - * as((int("id") <~ str("val")).single) - * // row has to have an int column 'id' and a string 'val' one, - * // keeping only 'id' in result - * }}} - */ - def <~[B](p: RowParser[B]): RowParser[A] = parent.~(p).map(_._1) - - // TODO: Scaladoc - def |[B >: A](p: RowParser[B]): RowParser[B] = RowParser { row => - parent(row) match { - case Error(_) => p(row) - case a => a - } - } - - /** - * Returns a row parser for optional column, - * that will turn missing or null column as None. - */ - def ? : RowParser[Option[A]] = RowParser { - parent(_) match { - case Success(a) => Success(Some(a)) - case Error(ColumnNotFound(_, _)) => - Success(None) - - case e @ Error(_) => e - } - } - - /** Alias for [[flatMap]] */ - def >>[B](f: A => RowParser[B]): RowParser[B] = flatMap(f) - - /** - * Returns possibly empty list parsed from result. - * - * {{{ - * import anorm._, SqlParser.scalar - * - * val price = 125 - * - * def foo(implicit con: java.sql.Connection) = - * SQL"SELECT name FROM item WHERE price < \\$price".as(scalar[String].*) - * }}} - */ - def * : ResultSetParser[List[A]] = ResultSetParser.list(parent) - - /** - * Returns non empty list parse from result, - * or raise error if there is no result. - * - * {{{ - * import anorm._, SqlParser.str - * - * def foo(implicit con: java.sql.Connection) = { - * val parser = str("title") ~ str("descr") - * SQL("SELECT title, descr FROM pages").as(parser.+) // at least 1 page - * } - * }}} - */ - def + : ResultSetParser[List[A]] = ResultSetParser.nonEmptyList(parent) - - /** - * Returns a result set parser expecting exactly one row to parse. - * - * {{{ - * import anorm._, SqlParser.scalar - * - * def b(implicit con: java.sql.Connection): Boolean = - * SQL("SELECT flag FROM Test WHERE id = :id"). - * on("id" -> 1).as(scalar[Boolean].single) - * }}} - * - * @see #singleOpt - */ - def single = ResultSetParser.single(parent) - - /** - * Returns a result set parser for none or one parsed row. - * - * {{{ - * import anorm._, SqlParser.scalar - * - * def name(implicit con: java.sql.Connection): Option[String] = - * SQL("SELECT name FROM Country WHERE lang = :lang") - * .on("lang" -> "notFound").as(scalar[String].singleOpt) - * }}} - */ - def singleOpt: ResultSetParser[Option[A]] = ResultSetParser.singleOpt(parent) - -} - /** Parser for scalar row (row of one single column). */ sealed trait ScalarRowParser[+A] extends RowParser[A] { override def singleOpt: ResultSetParser[Option[A]] = ResultSetParser { diff --git a/core/src/main/scala/anorm/SqlResult.scala b/core/src/main/scala/anorm/SqlResult.scala index 39d525e3..d56cf80a 100644 --- a/core/src/main/scala/anorm/SqlResult.scala +++ b/core/src/main/scala/anorm/SqlResult.scala @@ -14,6 +14,20 @@ sealed trait SqlResult[+A] { self => case e @ Error(_) => e } + def collect[B](f: PartialFunction[A, B]): SqlResult[B] = self match { + case Success(a) => + f.lift(a) match { + case Some(b) => + Success(b) + + case None => + Error(SqlMappingError(s"Value ${a} is not matching")) + } + + case Error(cause) => + Error(cause) + } + /** * Either applies function `e` if result is erroneous, * or function `f` with successful result if any. diff --git a/core/src/main/scala/anorm/ToStatementMisc.scala b/core/src/main/scala/anorm/ToStatementMisc.scala index d46e03fc..2746104b 100644 --- a/core/src/main/scala/anorm/ToStatementMisc.scala +++ b/core/src/main/scala/anorm/ToStatementMisc.scala @@ -32,8 +32,11 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `LONGVARBINARY` is called on statement. * * {{{ + * // skip-doc-5f98a5e + * import anorm._ + * * def foo(inputStream: java.io.InputStream) = - * anorm.SQL("INSERT INTO Table(bin) VALUES {b}").on("b" -> inputStream) + * SQL("INSERT INTO Table(bin) VALUES {b}").on("b" -> inputStream) * }}} */ implicit def binaryStreamToStatement[S <: InputStream]: ToStatement[S] = @@ -49,11 +52,14 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `BLOB` is called on statement. * * {{{ + * // skip-doc-5f98a5e + * import anorm._ + * * def foo(byteArray: Array[Byte])(implicit con: java.sql.Connection) = { * val blob = con.createBlob() * blob.setBytes(1, byteArray) * - * anorm.SQL("INSERT INTO Table(bin) VALUES {b}").on("b" -> blob) + * SQL("INSERT INTO Table(bin) VALUES {b}").on("b" -> blob) * } * }}} */ @@ -70,8 +76,11 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `VARCHAR` is called on statement. * * {{{ + * // skip-doc-5f98a5e + * import anorm._ + * * def foo(reader: java.io.Reader) = - * anorm.SQL("INSERT INTO Table(chars) VALUES {c}").on("c" -> reader) + * SQL("INSERT INTO Table(chars) VALUES {c}").on("c" -> reader) * }}} */ implicit def characterStreamToStatement[R <: Reader]: ToStatement[R] = @@ -86,7 +95,9 @@ sealed trait ToStatementPriority0 { * Sets boolean value on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE enabled = {b}").on("b" -> true) + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE enabled = {b}").on("b" -> true) * }}} */ implicit object booleanToStatement extends ToStatement[Boolean] { @@ -98,7 +109,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `BOOLEAN` is called on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE enabled = {b}"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE enabled = {b}"). * on("b" -> java.lang.Boolean.TRUE) * }}} */ @@ -112,7 +125,9 @@ sealed trait ToStatementPriority0 { * Sets byte value on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1.toByte) + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1.toByte) * }}} */ implicit object byteToStatement extends ToStatement[Byte] { @@ -124,7 +139,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `TINYINT` is called on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}"). * on("b" -> new java.lang.Byte(1: Byte)) * }}} */ @@ -138,7 +155,9 @@ sealed trait ToStatementPriority0 { * Sets double value on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1D) + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1D) * }}} */ implicit object doubleToStatement extends ToStatement[Double] { @@ -150,7 +169,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `DOUBLE` is called on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}"). * on("b" -> new java.lang.Double(1D)) * }}} */ @@ -164,7 +185,9 @@ sealed trait ToStatementPriority0 { * Sets float value on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1F) + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1F) * }}} */ implicit object floatToStatement extends ToStatement[Float] { @@ -176,7 +199,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `FLOAT` is called on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}"). * on("b" -> new java.lang.Float(1F)) * }}} */ @@ -190,7 +215,9 @@ sealed trait ToStatementPriority0 { * Sets long value on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1L) + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1L) * }}} */ implicit object longToStatement extends ToStatement[Long] { @@ -202,7 +229,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `BIGINT` is called on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}"). * on("b" -> new java.lang.Long(1L)) * }}} */ @@ -216,7 +245,9 @@ sealed trait ToStatementPriority0 { * Sets integer value on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1) + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1) * }}} */ implicit object intToStatement extends ToStatement[Int] { @@ -228,7 +259,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `INTEGER` is called on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}"). * on("b" -> new java.lang.Integer(1)) * }}} */ @@ -242,7 +275,9 @@ sealed trait ToStatementPriority0 { * Sets short value on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1.toShort) + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}").on("b" -> 1.toShort) * }}} */ implicit object shortToStatement extends ToStatement[Short] { @@ -254,7 +289,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `SMALLINT` is called on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE flag = {b}"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE flag = {b}"). * on("b" -> new java.lang.Short(1.toShort)) * }}} */ @@ -269,7 +306,9 @@ sealed trait ToStatementPriority0 { * For `null` character, `setNull` with `VARCHAR` is called on statement. * * {{{ - * anorm.SQL("SELECT * FROM tbl WHERE flag = {c}"). + * import anorm._ + * + * SQL("SELECT * FROM tbl WHERE flag = {c}"). * on("c" -> new java.lang.Character('f')) * }}} */ @@ -284,7 +323,9 @@ sealed trait ToStatementPriority0 { * Sets character as parameter value. * * {{{ - * anorm.SQL("SELECT * FROM tbl WHERE flag = {c}").on("c" -> 'f') + * import anorm._ + * + * SQL("SELECT * FROM tbl WHERE flag = {c}").on("c" -> 'f') * }}} */ implicit object charToStatement extends ToStatement[Char] { @@ -297,7 +338,9 @@ sealed trait ToStatementPriority0 { * Value `null` is accepted. * * {{{ - * anorm.SQL("SELECT * FROM tbl WHERE name = {n}").on("n" -> "str") + * import anorm._ + * + * SQL("SELECT * FROM tbl WHERE name = {n}").on("n" -> "str") * }}} */ implicit object stringToStatement extends ToStatement[String] { @@ -308,7 +351,9 @@ sealed trait ToStatementPriority0 { * Sets null for None value. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE category = {c}").on("c" -> None) + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE category = {c}").on("c" -> None) * * // Rather use: * anorm.SQL("SELECT * FROM Test WHERE category = {c}"). @@ -325,7 +370,9 @@ sealed trait ToStatementPriority0 { * Sets not empty optional A inferred as Some[A]. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE category = {c}").on("c" -> Some("cat")) + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE category = {c}").on("c" -> Some("cat")) * }}} */ implicit def someToStatement[A](implicit c: ToStatement[A]): ToStatement[Some[A]] = @@ -356,7 +403,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `NUMERIC` is called on statement. * * {{{ - * anorm.SQL("UPDATE tbl SET max = {m}"). + * import anorm._ + * + * SQL("UPDATE tbl SET max = {m}"). * on("m" -> new java.math.BigInteger("15")) * }}} */ @@ -372,7 +421,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `NUMERIC` is called on statement. * * {{{ - * anorm.SQL("UPDATE tbl SET max = {m}").on("m" -> BigInt(15)) + * import anorm._ + * + * SQL("UPDATE tbl SET max = {m}").on("m" -> BigInt(15)) * }}} */ implicit object scalaBigIntegerToStatement extends ToStatement[BigInt] { @@ -387,7 +438,9 @@ sealed trait ToStatementPriority0 { * Value `null` is accepted. * * {{{ - * anorm.SQL("UPDATE tbl SET max = {m}"). + * import anorm._ + * + * SQL("UPDATE tbl SET max = {m}"). * on("m" -> new java.math.BigDecimal(10.02F)) * }}} */ @@ -401,7 +454,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `DECIMAL` is called on statement. * * {{{ - * anorm.SQL("UPDATE tbl SET max = {m}").on("m" -> BigDecimal(10.02F)) + * import anorm._ + * + * SQL("UPDATE tbl SET max = {m}").on("m" -> BigDecimal(10.02F)) * }}} */ implicit object scalaBigDecimalToStatement extends ToStatement[BigDecimal] { @@ -416,8 +471,10 @@ sealed trait ToStatementPriority0 { * Value `null` is accepted. * * {{{ + * import anorm._ + * * def foo(date: java.util.Date) = - * anorm.SQL("UPDATE tbl SET modified = {ts}"). + * SQL("UPDATE tbl SET modified = {ts}"). * on("ts" -> new java.sql.Timestamp(date.getTime)) * }}} */ @@ -431,7 +488,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `TIMESTAMP` is called on statement. * * {{{ - * anorm.SQL("UPDATE tbl SET modified = {d}").on("d" -> new java.util.Date()) + * import anorm._ + * + * SQL("UPDATE tbl SET modified = {d}").on("d" -> new java.util.Date()) * }}} */ implicit object dateToStatement extends ToStatement[java.util.Date] { @@ -447,12 +506,15 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `TIMESTAMP` is called on statement. * * {{{ + * // skip-doc-5f98a5e + * import anorm._ + * * val wrapper = new { * // Any value with a `.getTimestamp` * val getTimestamp = new java.sql.Timestamp(123L) * } * - * anorm.SQL("UPDATE tbl SET modified = {ts}").on("ts" -> wrapper) + * SQL("UPDATE tbl SET modified = {ts}").on("ts" -> wrapper) * }}} */ implicit def timestampWrapper1ToStatement[T <: TimestampWrapper1]: ToStatement[T] = new ToStatement[T] { @@ -472,7 +534,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `VARCHAR` is called on statement. * * {{{ - * anorm.SQL("INSERT INTO lang_tbl(id, name) VALUE ({i}, {n})"). + * import anorm._ + * + * SQL("INSERT INTO lang_tbl(id, name) VALUE ({i}, {n})"). * on("i" -> java.util.UUID.randomUUID(), "n" -> "lang") * }}} */ @@ -488,7 +552,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `VARCHAR` is called on statement. * * {{{ - * anorm.SQL("INSERT INTO lang_tbl(id, name) VALUE ({i}, {n})"). + * import anorm._ + * + * SQL("INSERT INTO lang_tbl(id, name) VALUE ({i}, {n})"). * on("i" -> new java.net.URI("https://github.com/playframework/"), * "n" -> "lang") * }}} @@ -505,7 +571,9 @@ sealed trait ToStatementPriority0 { * For `null` value, `setNull` with `VARCHAR` is called on statement. * * {{{ - * anorm.SQL("INSERT INTO lang_tbl(id, name) VALUE ({i}, {n})"). + * import anorm._ + * + * SQL("INSERT INTO lang_tbl(id, name) VALUE ({i}, {n})"). * on("i" -> new java.net.URL("https://github.com/playframework/"), * "n" -> "lang") * }}} @@ -522,7 +590,9 @@ sealed trait ToStatementPriority0 { * UNSAFE: It's set using `java.sql.PreparedStatement.setObject`. * * {{{ - * anorm.SQL("EXEC indexed_at {d}"). + * import anorm._ + * + * SQL("EXEC indexed_at {d}"). * on("d" -> anorm.Object(new java.util.Date())) * }}} */ @@ -535,7 +605,9 @@ sealed trait ToStatementPriority0 { * Sets multi-value parameter from list on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE cat IN ({categories})"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE cat IN ({categories})"). * on("categories" -> List(1, 3, 4)) * }}} */ @@ -545,7 +617,9 @@ sealed trait ToStatementPriority0 { * Sets multi-value parameter from sequence on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE cat IN ({categories})"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE cat IN ({categories})"). * on("categories" -> Seq("a", "b", "c")) * }}} */ @@ -555,7 +629,9 @@ sealed trait ToStatementPriority0 { * Sets multi-value parameter from set on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE cat IN ({categories})"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE cat IN ({categories})"). * on("categories" -> Set(1, 3, 4)) * }}} */ @@ -567,7 +643,9 @@ sealed trait ToStatementPriority0 { * {{{ * import scala.collection.immutable.SortedSet * - * anorm.SQL("SELECT * FROM Test WHERE cat IN ({categories})"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE cat IN ({categories})"). * on("categories" -> SortedSet("a", "b", "c")) * }}} */ @@ -584,7 +662,9 @@ sealed trait ToStatementPriority0 { * Sets multi-value parameter from vector on statement. * * {{{ - * anorm.SQL("SELECT * FROM Test WHERE cat IN ({categories})"). + * import anorm._ + * + * SQL("SELECT * FROM Test WHERE cat IN ({categories})"). * on("categories" -> Vector("a", "b", "c")) * }}} */ @@ -596,9 +676,9 @@ sealed trait ToStatementPriority0 { * (using [[SeqParameter]]). * * {{{ - * import anorm.SeqParameter + * import anorm._ * - * anorm.SQL("SELECT * FROM Test t WHERE {categories}"). + * SQL("SELECT * FROM Test t WHERE {categories}"). * on("categories" -> SeqParameter( * seq = Seq("a", "b", "c"), sep = " OR ", * pre = "EXISTS (SELECT NULL FROM j WHERE t.id=j.id AND name=", @@ -625,7 +705,9 @@ sealed trait ToStatementPriority0 { * Sets an array parameter on statement (see `java.sql.Array`). * * {{{ - * anorm.SQL("INSERT INTO Table(arr) VALUES {a}"). + * import anorm._ + * + * SQL("INSERT INTO Table(arr) VALUES {a}"). * on("a" -> Array("A", "2", "C")) * }}} */ @@ -789,8 +871,10 @@ sealed trait ToStatementPriority1 extends ToStatementPriority0 { * For `null` value, `setNull` with `LONGVARBINARY` is called on statement. * * {{{ + * import anorm._ + * * def foo(arrayOfBytes: Array[Byte]) = - * anorm.SQL("INSERT INTO Table(bin) VALUES {b}").on("b" -> arrayOfBytes) + * SQL("INSERT INTO Table(bin) VALUES {b}").on("b" -> arrayOfBytes) * }}} */ implicit object byteArrayToStatement extends ToStatement[Array[Byte]] { diff --git a/core/src/main/scala/anorm/package.scala b/core/src/main/scala/anorm/package.scala index 221c4ade..5d7e40e3 100644 --- a/core/src/main/scala/anorm/package.scala +++ b/core/src/main/scala/anorm/package.scala @@ -4,8 +4,8 @@ import java.util.StringTokenizer -import java.sql.{ PreparedStatement, ResultSet, SQLException } import java.lang.reflect.InvocationTargetException +import java.sql.{ PreparedStatement, ResultSet, SQLException } import scala.reflect.ClassTag diff --git a/core/src/test/scala-2/anorm/AnormCompatSpec.scala b/core/src/test/scala-2/anorm/AnormCompatSpec.scala index 7dcb40bf..50be903a 100644 --- a/core/src/test/scala-2/anorm/AnormCompatSpec.scala +++ b/core/src/test/scala-2/anorm/AnormCompatSpec.scala @@ -1,9 +1,8 @@ package anorm +import acolyte.jdbc.AcolyteDSL.withQueryResult import acolyte.jdbc.Implicits._ - import acolyte.jdbc.RowLists -import acolyte.jdbc.AcolyteDSL.withQueryResult private[anorm] trait AnormCompatSpec { spec: AnormSpec => "Query (scala2)" should { diff --git a/core/src/test/scala/anorm/AnormSpec.scala b/core/src/test/scala/anorm/AnormSpec.scala index 4cea9f4a..f30be3a9 100644 --- a/core/src/test/scala/anorm/AnormSpec.scala +++ b/core/src/test/scala/anorm/AnormSpec.scala @@ -54,7 +54,7 @@ final class AnormSpec extends Specification with H2Database with AnormTest { } "return 0 for missing optional numeric" in withQueryResult(null.asInstanceOf[Double]) { implicit c: Connection => - SQL("SELECT * FROM test").as(scalar[Double].singleOpt).aka("single value") must beSome(0d) + SQL("SELECT * FROM test").as(scalar[Double].singleOpt).aka("single value") must beSome(0D) } diff --git a/core/src/test/scala/anorm/FunctionAdapterSpec.scala b/core/src/test/scala/anorm/FunctionAdapterSpec.scala index 75ccc83c..d6e05802 100644 --- a/core/src/test/scala/anorm/FunctionAdapterSpec.scala +++ b/core/src/test/scala/anorm/FunctionAdapterSpec.scala @@ -50,7 +50,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { val schema = rowList4(classOf[String] -> "A", classOf[Int] -> "B", classOf[Long] -> "C", classOf[Double] -> "D") - withQueryResult(schema.append("A", 2, 3L, 4.56d)) { implicit c: Connection => + withQueryResult(schema.append("A", 2, 3L, 4.56D)) { implicit c: Connection => SQL("SELECT * FROM test") .as((str("A") ~ int("B") ~ long("C") ~ get[Double]("D")).map(SqlParser.to(foo _)).single) .aka("function result") must_=== "Fn4" @@ -69,7 +69,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { classOf[Short] -> "E" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort)) { implicit c: Connection => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort)) { implicit c: Connection => SQL("SELECT * FROM test") .as((str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E")).map(SqlParser.to(foo _)).single) .aka("function result") must_=== "Fn5" @@ -89,7 +89,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { classOf[Byte] -> "F" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte)) { implicit c: Connection => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte)) { implicit c: Connection => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F")) @@ -114,7 +114,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { classOf[Boolean] -> "G" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true)) { implicit c: Connection => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true)) { implicit c: Connection => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F") ~ bool("G")) @@ -140,7 +140,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { classOf[String] -> "H" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B")) { implicit c: Connection => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B")) { implicit c: Connection => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F") ~ bool("G") ~ str( @@ -167,7 +167,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { classOf[Int] -> "I" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3)) { implicit c: Connection => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3)) { implicit c: Connection => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F") ~ bool("G") ~ str( @@ -206,7 +206,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { classOf[Long] -> "J" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L)) { + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L)) { implicit c: Connection => SQL("SELECT * FROM test") .as( @@ -248,7 +248,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { classOf[Double] -> "K" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d)) { + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D)) { implicit c: Connection => SQL("SELECT * FROM test") .as( @@ -292,7 +292,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { classOf[Short] -> "L" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d, 10.toShort)) { + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D, 10.toShort)) { implicit c: Connection => SQL("SELECT * FROM test") .as( @@ -339,7 +339,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { ) withQueryResult( - schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d, 10.toShort, 11.toByte) + schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D, 10.toShort, 11.toByte) ) { implicit c: Connection => SQL("SELECT * FROM test") .as( @@ -390,7 +390,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { ) withQueryResult( - schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d, 10.toShort, 11.toByte, false) + schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D, 10.toShort, 11.toByte, false) ) { implicit c: Connection => SQL("SELECT * FROM test") .as( @@ -444,7 +444,7 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { withQueryResult( schema - .append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d, 10.toShort, 11.toByte, false, "C") + .append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D, 10.toShort, 11.toByte, false, "C") ) { implicit c: Connection => SQL("SELECT * FROM test") .as( @@ -503,14 +503,14 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, @@ -577,14 +577,14 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, @@ -654,21 +654,21 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d + 5.678D ) ) { implicit c: Connection => SQL("SELECT * FROM test") @@ -734,21 +734,21 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort ) ) { implicit c: Connection => @@ -817,21 +817,21 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort, "D" ) @@ -903,21 +903,21 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort, "D", 4 @@ -992,21 +992,21 @@ final class FunctionAdapterSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort, "D", 4, diff --git a/core/src/test/scala/anorm/JavaTimeSpec.scala b/core/src/test/scala/anorm/JavaTimeSpec.scala index 9ba7c83d..b462d62f 100644 --- a/core/src/test/scala/anorm/JavaTimeSpec.scala +++ b/core/src/test/scala/anorm/JavaTimeSpec.scala @@ -1,8 +1,7 @@ package anorm import java.sql.{ Connection, Timestamp } - -import java.time.{ Instant, LocalDate, LocalDateTime, ZonedDateTime, ZoneId } +import java.time.{ Instant, LocalDate, LocalDateTime, ZoneId, ZonedDateTime } import acolyte.jdbc.AcolyteDSL._ import acolyte.jdbc.Implicits._ diff --git a/core/src/test/scala/anorm/MacroSpec.scala b/core/src/test/scala/anorm/MacroSpec.scala index 78230fae..694feecd 100644 --- a/core/src/test/scala/anorm/MacroSpec.scala +++ b/core/src/test/scala/anorm/MacroSpec.scala @@ -7,6 +7,7 @@ import acolyte.jdbc.{ DefinedParameter => DParam, ParameterMetaData => ParamMeta import acolyte.jdbc.AcolyteDSL.{ connection, handleStatement, withQueryResult } import acolyte.jdbc.Implicits._ +import org.specs2.matcher.TypecheckMatchers._ import org.specs2.specification.core.Fragments import com.github.ghik.silencer.silent @@ -17,6 +18,8 @@ import SqlParser.scalar final class MacroSpec extends org.specs2.mutable.Specification { "Macro".title + import TestUtils.typecheck + val barRow1 = RowLists.rowList1(classOf[Int] -> "v") val fooRow1 = RowLists.rowList5( @@ -35,7 +38,6 @@ final class MacroSpec extends org.specs2.mutable.Specification { classOf[JBool] -> "x" ) // java types to avoid conv - /* TODO "Column naming" should { import ColumnNaming._ @@ -49,49 +51,61 @@ final class MacroSpec extends org.specs2.mutable.Specification { } "Generated named parser" should { - // No Column[Bar] so compilation error is expected - shapeless.test.illTyped("anorm.Macro.namedParser[Foo[Bar]]") - - // Not enough column names for class parameters - shapeless.test.illTyped("""anorm.Macro.parser[Foo[Int]]("Foo", "Bar")""") + "not be resolved" in { + // No Column[Bar] so compilation error is expected + (typecheck("anorm.Macro.namedParser[Foo[Bar]]") must failWith( + ".*cannot find.* .*Column.* nor .*RowParser.* for .*loremIpsum.*Bar.*" + )).and { + // Not enough column names for class parameters + typecheck("""anorm.Macro.parser[Foo[Int]]("Foo", "Bar")""") must failWith( + ".*no column name for parameters.* .*Foo.* .*Bar.*" + ) + } + } "be successful for Bar" in withQueryResult(barRow1 :+ 1 :+ 3) { implicit c => val parser1 = Macro.namedParser[Bar] val parser2 = Macro.parser[Bar]("v") - (SQL"TEST".as(parser1.*) must_=== List(Bar(1), Bar(3))).and(SQL"TEST".as(parser2.*) must_=== List(Bar(1), Bar(3))) + (SQL"TEST".as(parser1.*) must_=== List(Bar(1), Bar(3))).and { + SQL"TEST".as(parser2.*) must_=== List(Bar(1), Bar(3)) + } } "be successful for Foo[Int]" >> { def spec(parser1: RowParser[Foo[Int]], parser2: RowParser[Foo[Int]])(implicit c: Connection) = { val expected = List( - Foo(1.2f, "str1")(1, Some(2L))(Some(true)), - Foo(2.3f, "str2")(4, None)(None), - Foo(3.4f, "str3")(5, Some(3L))(None), - Foo(5.6f, "str4")(6, None)(Some(false)) + Foo(1.2F, "str1")(1, Some(2L))(Some(true)), + Foo(2.3F, "str2")(4, None)(None), + Foo(3.4F, "str3")(5, Some(3L))(None), + Foo(5.6F, "str4")(6, None)(Some(false)) ) - (SQL"TEST".as(parser1.*) must_=== expected).and(SQL("TEST").as(parser2.*) must_=== expected) + (SQL"TEST".as(parser1.*) must_=== expected).and { + SQL("TEST").as(parser2.*) must_=== expected + } } "using the default column naming" in withQueryResult( fooRow1 - .append(1.2f, "str1", 1, 2L, true) - .append(2.3f, "str2", 4, nullLong, nullBoolean) - .append(3.4f, "str3", 5, 3L, nullBoolean) - .append(5.6f, "str4", 6, nullLong, false) + .append(1.2F, "str1", 1, 2L, true) + .append(2.3F, "str2", 4, nullLong, nullBoolean) + .append(3.4F, "str3", 5, 3L, nullBoolean) + .append(5.6F, "str4", 6, nullLong, false) ) { implicit con => - spec(Macro.namedParser[Foo[Int]], Macro.parser[Foo[Int]]("r", "bar", "loremIpsum", "opt", "x")) - + spec( + parser1 = Macro.namedParser[Foo[Int]], + parser2 = Macro.parser[Foo[Int]]("r", "bar", "loremIpsum", "opt", "x") + ) } "using the snake case naming" in withQueryResult( fooRow2 - .append(1.2f, "str1", 1, 2L, true) - .append(2.3f, "str2", 4, nullLong, nullBoolean) - .append(3.4f, "str3", 5, 3L, nullBoolean) - .append(5.6f, "str4", 6, nullLong, false) + .append(1.2F, "str1", 1, 2L, true) + .append(2.3F, "str2", 4, nullLong, nullBoolean) + .append(3.4F, "str3", 5, 3L, nullBoolean) + .append(5.6F, "str4", 6, nullLong, false) ) { implicit con => spec( @@ -115,21 +129,26 @@ final class MacroSpec extends org.specs2.mutable.Specification { classOf[Int] -> "v" ) - withQueryResult(row.append(1.2f, "str1", 1, 2L, true, 6)) { implicit c => - SQL"TEST".as(fooBar.singleOpt) must beSome(Foo(1.2f, "str1")(Bar(6), Some(2))(Some(true))) + withQueryResult(row.append(1.2F, "str1", 1, 2L, true, 6)) { implicit c => + SQL"TEST".as(fooBar.singleOpt) must beSome(Foo(1.2F, "str1")(Bar(6), Some(2))(Some(true))) } } "support self reference" in { - val _ = Macro.namedParser[Self] // check compile is ok + // check compile is ok + typecheck("Macro.namedParser[Self]") must not(beNull) - ok // TODO: Supports aliasing to make it really usable (see #124) + // TODO: Supports aliasing to make it really usable (see #124) } } "Generated indexed parser" should { - // No Column[Bar] so compilation error is expected - shapeless.test.illTyped("anorm.Macro.indexedParser[Foo[Bar]]") + "not be resolved" in { + // No Column[Bar] so compilation error is expected + typecheck("anorm.Macro.indexedParser[Foo[Bar]]") must failWith( + ".*cannot find .*Column.* nor .*RowParser.* .*loremIpsum.*Bar.*" + ) + } "be successful for Bar" in withQueryResult(RowLists.intList :+ 1 :+ 3) { implicit c => SQL"TEST".as(Macro.indexedParser[Bar].*) must_=== List(Bar(1), Bar(3)) @@ -137,25 +156,29 @@ final class MacroSpec extends org.specs2.mutable.Specification { "be successful for Foo[Int]" in withQueryResult( fooRow1 - .append(1.2f, "str1", 1, 2L, true) - .append(2.3f, "str2", 4, nullLong, nullBoolean) - .append(3.4f, "str3", 5, 3L, nullBoolean) - .append(5.6f, "str4", 6, nullLong, false) + .append(1.2F, "str1", 1, 2L, true) + .append(2.3F, "str2", 4, nullLong, nullBoolean) + .append(3.4F, "str3", 5, 3L, nullBoolean) + .append(5.6F, "str4", 6, nullLong, false) ) { implicit con => val parser: RowParser[Foo[Int]] = Macro.indexedParser[Foo[Int]] SQL"TEST".as(parser.*) must_=== List( - Foo(1.2f, "str1")(1, Some(2L))(Some(true)), - Foo(2.3f, "str2")(4, None)(None), - Foo(3.4f, "str3")(5, Some(3L))(None), - Foo(5.6f, "str4")(6, None)(Some(false)) + Foo(1.2F, "str1")(1, Some(2L))(Some(true)), + Foo(2.3F, "str2")(4, None)(None), + Foo(3.4F, "str3")(5, Some(3L))(None), + Foo(5.6F, "str4")(6, None)(Some(false)) ) } } "Generated indexed parser (with an offset)" should { - // No Column[Bar] so compilation error is expected - shapeless.test.illTyped("anorm.Macro.offsetParser[Foo[Bar]]") + "not be resolved" in { + // No Column[Bar] so compilation error is expected + typecheck("Macro.offsetParser[Foo[Bar]](1)") must failWith( + ".*cannot find .*Column.* nor .*RowParser.* for loremIpsum.*Bar.* .*Foo.*" + ) + } "be successful for Bar" in withQueryResult(RowLists.intList :+ 1 :+ 3) { implicit c => SQL"TEST".as(Macro.offsetParser[Bar](0).*) must_=== List(Bar(1), Bar(3)) @@ -163,27 +186,27 @@ final class MacroSpec extends org.specs2.mutable.Specification { "be successful for Foo[Int]" in withQueryResult( fooRow1 - .append(1.2f, "str1", 1, 2L, true) - .append(2.3f, "str2", 4, nullLong, nullBoolean) - .append(3.4f, "str3", 5, 3L, nullBoolean) - .append(5.6f, "str4", 6, nullLong, false) + .append(1.2F, "str1", 1, 2L, true) + .append(2.3F, "str2", 4, nullLong, nullBoolean) + .append(3.4F, "str3", 5, 3L, nullBoolean) + .append(5.6F, "str4", 6, nullLong, false) ) { implicit con => val parser: RowParser[Foo[Int]] = Macro.offsetParser[Foo[Int]](0) SQL"TEST".as(parser.*) must_=== List( - Foo(1.2f, "str1")(1, Some(2L))(Some(true)), - Foo(2.3f, "str2")(4, None)(None), - Foo(3.4f, "str3")(5, Some(3L))(None), - Foo(5.6f, "str4")(6, None)(Some(false)) + Foo(1.2F, "str1")(1, Some(2L))(Some(true)), + Foo(2.3F, "str2")(4, None)(None), + Foo(3.4F, "str3")(5, Some(3L))(None), + Foo(5.6F, "str4")(6, None)(Some(false)) ) } "be successful for Goo[T] with offset = 2" in withQueryResult( fooRow1 - .append(1.2f, "str1", 1, 2L, true) - .append(2.3f, "str2", 4, nullLong, nullBoolean) - .append(3.4f, "str3", 5, 3L, nullBoolean) - .append(5.6f, "str4", 6, nullLong, false) + .append(1.2F, "str1", 1, 2L, true) + .append(2.3F, "str2", 4, nullLong, nullBoolean) + .append(3.4F, "str3", 5, 3L, nullBoolean) + .append(5.6F, "str4", 6, nullLong, false) ) { implicit con => val parser: RowParser[Goo[Int]] = Macro.offsetParser[Goo[Int]](2) @@ -223,15 +246,18 @@ final class MacroSpec extends org.specs2.mutable.Specification { } "Sealed parser" should { - // No subclass - shapeless.test.illTyped("anorm.Macro.sealedParser[NoSubclass]") - - // Cannot find the RowParser instances for the subclasses, - // from the implicit scope - shapeless.test.illTyped("Macro.sealedParser[Family]") - - // No subclass - shapeless.test.illTyped("Macro.sealedParser[EmptyFamily]") + "not be resolved" in { + (typecheck("Macro.sealedParser[NoSubclass]") must failWith(".*cannot find any subclass.* .*NoSubclass.*")) + .and { + // Cannot find the RowParser instances for the subclasses, + // from the implicit scope + typecheck("Macro.sealedParser[Family]") must failWith(".*sealed.* .*Bar.* .*CaseObj.*") + } + .and { + // No subclass + typecheck("Macro.sealedParser[EmptyFamily]") must failWith(".*cannot find any subclass.* .*EmptyFamily.*") + } + } "be successful for the Family trait" >> { "with the default discrimination" in { @@ -241,7 +267,8 @@ final class MacroSpec extends org.specs2.mutable.Specification { implicit val caseObjParser = RowParser[CaseObj.type] { _ => Success(CaseObj) } - implicit val barParser = Macro.namedParser[Bar] + implicit val barParser: RowParser[Bar] = + SqlParser.int("v").map { Bar(_) } // cannot handle object anorm.MacroSpec.NotCase: no case accessor @silent def familyParser = Macro.sealedParser[Family] @@ -257,7 +284,8 @@ final class MacroSpec extends org.specs2.mutable.Specification { implicit val caseObjParser = RowParser[CaseObj.type] { _ => Success(CaseObj) } - implicit val barParser = Macro.namedParser[Bar] + implicit val barParser: RowParser[Bar] = + SqlParser.int("v").map { Bar(_) } // cannot handle object anorm.MacroSpec.NotCase: no case accessor @silent def familyParser = @@ -273,8 +301,11 @@ final class MacroSpec extends org.specs2.mutable.Specification { import Macro.{ ParameterProjection => proj } import NamedParameter.{ namedWithString => named } - // No ToParameterList[Bar] so compilation error is expected - shapeless.test.illTyped("anorm.Macro.toParameters[Goo[Bar]]") + "not be resolved" in { + typecheck("Macro.toParameters[Goo[Bar]]") must failWith( + ".*cannot find either .*ToParameterList.* or .*ToStatement.* for .*Bar.*" + ) + } "be successful for Bar" >> { val fixture = Bar(1) @@ -284,10 +315,11 @@ final class MacroSpec extends org.specs2.mutable.Specification { Macro.toParameters[Bar]() -> List(named("v" -> 1)), Macro.toParameters[Bar](proj("v", "w")) -> List(named("w" -> 1)) ).zipWithIndex - ) { case ((encoder, params), index) => - s"using encoder #${index}" in { - encoder(fixture) must_=== params - } + ) { + case ((encoder, params), index) => + s"using encoder #${index}" in { + encoder(fixture) must_=== params + } } } @@ -303,10 +335,11 @@ final class MacroSpec extends org.specs2.mutable.Specification { ), Macro.toParameters[Goo[Int]](proj("loremIpsum", "value")) -> List(named("value" -> 1)) ).zipWithIndex - ) { case ((encoder, params), index) => - s"using encoder #${index}" in { - encoder(fixture) must_=== params - } + ) { + case ((encoder, params), index) => + s"using encoder #${index}" in { + encoder(fixture) must_=== params + } } } @@ -321,10 +354,11 @@ final class MacroSpec extends org.specs2.mutable.Specification { Fragments.foreach( Seq[(Family, List[NamedParameter])](Bar(1) -> List(named("v" -> 1)), CaseObj -> List.empty[NamedParameter]) - ) { case (i, params) => - s"for $i" in { - ToParameterList.from(i) must_=== params - } + ) { + case (i, params) => + s"for $i" in { + ToParameterList.from(i) must_=== params + } } } @@ -346,37 +380,41 @@ final class MacroSpec extends org.specs2.mutable.Specification { named("x" -> Some(false)) ) ).zipWithIndex - ) { case ((encoder, params), index) => - s"using encoder #${index}" in { - encoder(fixture) must_=== params - } + ) { + case ((encoder, params), index) => + s"using encoder #${index}" in { + encoder(fixture) must_=== params + } } } } -*/ "Generated column" should { - shapeless.test.illTyped("anorm.Macro.valueColumn[Bar]") // case class - - shapeless.test.illTyped("anorm.Macro.valueColumn[InvalidValueClass]") + "not be resolved" in { + (typecheck("Macro.valueColumn[Bar]") must failWith(".*AnyVal.*")).and { + typecheck("Macro.valueColumn[InvalidValueClass]") must failWith(".*MacroSpec.*") + } + } "be generated for a supported ValueClass" in { implicit val generated: Column[ValidValueClass] = Macro.valueColumn[ValidValueClass] - withQueryResult(RowLists.doubleList :+ 1.2d) { implicit con => - SQL("SELECT d").as(scalar[ValidValueClass].single).aka("parsed column") must_=== new ValidValueClass(1.2d) + withQueryResult(RowLists.doubleList :+ 1.2D) { implicit con => + SQL("SELECT d").as(scalar[ValidValueClass].single).aka("parsed column") must_=== new ValidValueClass(1.2D) } } } "ToStatement" should { - shapeless.test.illTyped("anorm.Macro.valueToStatement[Bar]") // case class + "not be resolved" in { + typecheck("Macro.valueToStatement[Bar]") must failWith(".*AnyVal.*") + } - val SqlDouble3s = ParamMeta.Double(23.456d) + val SqlDouble3s = ParamMeta.Double(23.456D) def withConnection[A](f: java.sql.Connection => A): A = f(connection(handleStatement.withUpdateHandler { - case UpdateExecution("set-double ?", DParam(23.456d, SqlDouble3s) :: Nil) => 1 /* case ok */ + case UpdateExecution("set-double ?", DParam(23.456D, SqlDouble3s) :: Nil) => 1 /* case ok */ case x => sys.error(s"Unexpected: $x") })) @@ -386,7 +424,7 @@ final class MacroSpec extends org.specs2.mutable.Specification { Macro.valueToStatement[ValidValueClass] withConnection { implicit c => - SQL("set-double {p}").on("p" -> new ValidValueClass(23.456d)).execute() must beFalse + SQL("set-double {p}").on("p" -> new ValidValueClass(23.456D)).execute() must beFalse } } } diff --git a/core/src/test/scala/anorm/RowSpec.scala b/core/src/test/scala/anorm/RowSpec.scala index be52db01..68cc892d 100644 --- a/core/src/test/scala/anorm/RowSpec.scala +++ b/core/src/test/scala/anorm/RowSpec.scala @@ -44,6 +44,7 @@ final class RowSpec extends org.specs2.mutable.Specification { val meta2 = MetaDataItem(ColumnName("table.id", Some("second_id")), false, "java.lang.Integer") val metaData = MetaData(List(meta1, meta2)) val row = ResultRow(metaData, List(1, 2)) + row.get("second_id") must beRight((2, meta2)) } @@ -53,6 +54,7 @@ final class RowSpec extends org.specs2.mutable.Specification { val meta2 = MetaDataItem(ColumnName("data.name", Some("CorrectAlias")), false, "java.lang.String") val metaData = MetaData(List(meta1, meta2)) val row = ResultRow(metaData, List("IncorrectString", "CorrectString")) + row.get("CorrectAlias") must beRight(("CorrectString", meta2)) } } diff --git a/core/src/test/scala/anorm/SqlResultSpec.scala b/core/src/test/scala/anorm/SqlResultSpec.scala index 2d997abf..edc98163 100644 --- a/core/src/test/scala/anorm/SqlResultSpec.scala +++ b/core/src/test/scala/anorm/SqlResultSpec.scala @@ -21,7 +21,7 @@ final class SqlResultSpec extends org.specs2.mutable.Specification with H2Databa private implicit val tag: ClassTag[Closeable] = Resources.closeableTag - "For-comprehension over result" should { + "For-comprehension/map over result" should { "fail when there is no data" in withQueryResult("scalar") { implicit c: Connection => lazy val parser = for { a <- SqlParser.str("col1") @@ -105,33 +105,47 @@ final class SqlResultSpec extends org.specs2.mutable.Specification with H2Databa } } + "Successful result" in { + "be collected" in { + val suc = anorm.Success("foo") + + (suc.collect { + case "foo" => 1 + } must_=== anorm.Success(1)).and { + suc.collect { + case "bar" => 1 + } must_=== anorm.Error(SqlMappingError("Value foo is not matching")) + } + } + } + "Column" should { - "be found in result" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2f) { implicit c: Connection => - SQL("SELECT f").as(SqlParser.matches("f", 1.2f).single) must beTrue + "be found in result" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2F) { implicit c: Connection => + SQL("SELECT f").as(SqlParser.matches("f", 1.2F).single) must beTrue } - "not be found in result when value not matching" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2f) { + "not be found in result when value not matching" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2F) { implicit c: Connection => - SQL("SELECT f").as(SqlParser.matches("f", 2.34f).single) must beFalse + SQL("SELECT f").as(SqlParser.matches("f", 2.34F).single) must beFalse } - "not be found in result when column missing" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2f) { + "not be found in result when column missing" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2F) { implicit c: Connection => - SQL("SELECT f").as(SqlParser.matches("x", 1.2f).single) must beFalse + SQL("SELECT f").as(SqlParser.matches("x", 1.2F).single) must beFalse } - "be matching in result" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2f) { implicit c: Connection => - SQL("SELECT f").as(SqlParser.matches("f", 1.2f).single) must beTrue + "be matching in result" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2F) { implicit c: Connection => + SQL("SELECT f").as(SqlParser.matches("f", 1.2F).single) must beTrue } - "not be found in result when value not matching" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2f) { + "not be found in result when value not matching" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2F) { implicit c: Connection => - SQL("SELECT f").as(SqlParser.matches("f", 2.34f).single) must beFalse + SQL("SELECT f").as(SqlParser.matches("f", 2.34F).single) must beFalse } - "not be found in result when column missing" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2f) { + "not be found in result when column missing" in withQueryResult(rowList1(classOf[Float] -> "f") :+ 1.2F) { implicit c: Connection => - SQL("SELECT f").as(SqlParser.matches("x", 1.2f).single) must beFalse + SQL("SELECT f").as(SqlParser.matches("x", 1.2F).single) must beFalse } "be None when missing" in withQueryResult(rowList1(classOf[String] -> "foo") :+ "bar") { implicit c: Connection => diff --git a/core/src/test/scala/anorm/TupleFlattenerSpec.scala b/core/src/test/scala/anorm/TupleFlattenerSpec.scala index 2c4ceb89..299b3305 100644 --- a/core/src/test/scala/anorm/TupleFlattenerSpec.scala +++ b/core/src/test/scala/anorm/TupleFlattenerSpec.scala @@ -35,10 +35,10 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "be flatten from 4 columns to Tuple4" in { val schema = rowList4(classOf[String] -> "A", classOf[Int] -> "B", classOf[Long] -> "C", classOf[Double] -> "D") - withQueryResult(schema.append("A", 2, 3L, 4.56d)) { implicit c => + withQueryResult(schema.append("A", 2, 3L, 4.56D)) { implicit c => SQL("SELECT * FROM test") .as((str("A") ~ int("B") ~ long("C") ~ get[Double]("D")).map(SqlParser.flatten).single) - .aka("flatten columns") must_=== Tuple4("A", 2, 3L, 4.56d) + .aka("flatten columns") must_=== Tuple4("A", 2, 3L, 4.56D) } } @@ -52,10 +52,10 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { classOf[Short] -> "E" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort)) { implicit c => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort)) { implicit c => SQL("SELECT * FROM test") .as((str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E")).map(SqlParser.flatten).single) - .aka("flatten columns") must_=== Tuple5("A", 2, 3L, 4.56d, 9.toShort) + .aka("flatten columns") must_=== Tuple5("A", 2, 3L, 4.56D, 9.toShort) } } @@ -70,14 +70,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { classOf[Byte] -> "F" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte)) { implicit c => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte)) { implicit c => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F")) .map(SqlParser.flatten) .single ) - .aka("flatten columns") must_=== Tuple6("A", 2, 3L, 4.56d, 9.toShort, 10.toByte) + .aka("flatten columns") must_=== Tuple6("A", 2, 3L, 4.56D, 9.toShort, 10.toByte) } } @@ -93,14 +93,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { classOf[Boolean] -> "G" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true)) { implicit c => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true)) { implicit c => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F") ~ bool("G")) .map(SqlParser.flatten) .single ) - .aka("flatten columns") must_=== Tuple7("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true) + .aka("flatten columns") must_=== Tuple7("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true) } } @@ -117,14 +117,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { classOf[String] -> "H" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B")) { implicit c => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B")) { implicit c => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F") ~ bool("G") ~ str( "H" )).map(SqlParser.flatten).single ) - .aka("flatten columns") must_=== Tuple8("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B") + .aka("flatten columns") must_=== Tuple8("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B") } } @@ -142,14 +142,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { classOf[Int] -> "I" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3)) { implicit c => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3)) { implicit c => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F") ~ bool("G") ~ str( "H" ) ~ int("I")).map(SqlParser.flatten).single ) - .aka("flatten columns") must_=== Tuple9("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3) + .aka("flatten columns") must_=== Tuple9("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3) } } @@ -168,14 +168,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { classOf[Long] -> "J" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L)) { implicit c => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L)) { implicit c => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F") ~ bool("G") ~ str( "H" ) ~ int("I") ~ long("J")).map(SqlParser.flatten).single ) - .aka("flatten columns") must_=== Tuple10("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L) + .aka("flatten columns") must_=== Tuple10("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L) } } @@ -195,14 +195,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { classOf[Double] -> "K" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d)) { implicit c => + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D)) { implicit c => SQL("SELECT * FROM test") .as( (str("A") ~ int("B") ~ long("C") ~ get[Double]("D") ~ get[Short]("E") ~ get[Byte]("F") ~ bool("G") ~ str( "H" ) ~ int("I") ~ long("J") ~ get[Double]("K")).map(SqlParser.flatten).single ) - .aka("flatten columns") must_=== Tuple11("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d) + .aka("flatten columns") must_=== Tuple11("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D) } } @@ -223,7 +223,7 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { classOf[Short] -> "L" ) - withQueryResult(schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d, 10.toShort)) { + withQueryResult(schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D, 10.toShort)) { implicit c => SQL("SELECT * FROM test") .as( @@ -235,14 +235,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort ) @@ -267,7 +267,7 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { ) withQueryResult( - schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d, 10.toShort, 11.toByte) + schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D, 10.toShort, 11.toByte) ) { implicit c => SQL("SELECT * FROM test") .as( @@ -281,14 +281,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte ) @@ -315,7 +315,7 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { ) withQueryResult( - schema.append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d, 10.toShort, 11.toByte, false) + schema.append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D, 10.toShort, 11.toByte, false) ) { implicit c => SQL("SELECT * FROM test") .as( @@ -329,14 +329,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false @@ -366,7 +366,7 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { withQueryResult( schema - .append("A", 2, 3L, 4.56d, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67d, 10.toShort, 11.toByte, false, "C") + .append("A", 2, 3L, 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, 5.67D, 10.toShort, 11.toByte, false, "C") ) { implicit c => SQL("SELECT * FROM test") .as( @@ -380,14 +380,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, @@ -422,14 +422,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, @@ -449,14 +449,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, @@ -493,14 +493,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, @@ -521,14 +521,14 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, @@ -567,21 +567,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d + 5.678D ) ) { implicit c => SQL("SELECT * FROM test") @@ -596,21 +596,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d + 5.678D ) } @@ -644,21 +644,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort ) ) { implicit c => @@ -674,21 +674,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort ) @@ -724,21 +724,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort, "D" ) @@ -755,21 +755,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort, "D" ) @@ -807,21 +807,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort, "D", 4 @@ -839,21 +839,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort, "D", 4 @@ -893,21 +893,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort, "D", 4, @@ -928,21 +928,21 @@ final class TupleFlattenerSpec extends org.specs2.mutable.Specification { "A", 2, 3L, - 4.56d, + 4.56D, 9.toShort, 10.toByte, true, "B", 3, 4L, - 5.67d, + 5.67D, 10.toShort, 11.toByte, false, "C", 3, 4L, - 5.678d, + 5.678D, 16.toShort, "D", 4, diff --git a/docs/manual/working/scalaGuide/main/sql/AnormEnumeratum.md b/docs/manual/working/scalaGuide/main/sql/AnormEnumeratum.md index b7fc8ef1..21d7bf97 100644 --- a/docs/manual/working/scalaGuide/main/sql/AnormEnumeratum.md +++ b/docs/manual/working/scalaGuide/main/sql/AnormEnumeratum.md @@ -12,6 +12,8 @@ libraryDependencies ++= Seq( ) ``` +> As Enumeratum itself is not yet compatible, this module is not available for Scala 3. + ## Usage Using this module, enums can be parsed from database columns and passed as parameters. diff --git a/enumeratum/src/main/scala/values/ValueEnumColumn.scala b/enumeratum/src/main/scala/values/ValueEnumColumn.scala index af2d0764..be284d3e 100644 --- a/enumeratum/src/main/scala/values/ValueEnumColumn.scala +++ b/enumeratum/src/main/scala/values/ValueEnumColumn.scala @@ -6,16 +6,17 @@ import anorm.{ Column, TypeDoesNotMatch } private[values] object ValueEnumColumn { def apply[ValueType, EntryType <: ValueEnumEntry[ValueType]](e: ValueEnum[ValueType, EntryType])(implicit baseColumn: Column[ValueType] - ): Column[EntryType] = Column.nonNull[EntryType] { case (value, meta) => - baseColumn(value, meta) match { - case Left(err) => - Left(err) + ): Column[EntryType] = Column.nonNull[EntryType] { + case (value, meta) => + baseColumn(value, meta) match { + case Left(err) => + Left(err) - case Right(s) => - e.withValueOpt(s) match { - case Some(obj) => Right(obj) - case None => Left(TypeDoesNotMatch(s"Invalid value: $s")) - } - } + case Right(s) => + e.withValueOpt(s) match { + case Some(obj) => Right(obj) + case None => Left(TypeDoesNotMatch(s"Invalid value: $s")) + } + } } } diff --git a/enumeratum/src/test/scala/EnumColumnSpec.scala b/enumeratum/src/test/scala/EnumColumnSpec.scala index 6b95e73f..eea263c5 100644 --- a/enumeratum/src/test/scala/EnumColumnSpec.scala +++ b/enumeratum/src/test/scala/EnumColumnSpec.scala @@ -28,8 +28,9 @@ final class EnumColumnSpec extends org.specs2.mutable.Specification { "not be parsed as Column from invalid String representation" >> { def spec(title: String, repr: String) = title in withQueryResult(RowLists.stringList :+ repr) { implicit con => - SQL("SELECT v").as(scalar[Dummy].single) must throwA[Exception].like { case NonFatal(cause) => - cause must_=== AnormException(TypeDoesNotMatch(s"Invalid value: $repr").message) + SQL("SELECT v").as(scalar[Dummy].single) must throwA[Exception].like { + case NonFatal(cause) => + cause must_=== AnormException(TypeDoesNotMatch(s"Invalid value: $repr").message) } } @@ -41,12 +42,15 @@ final class EnumColumnSpec extends org.specs2.mutable.Specification { "not be parsed as Column from non-String values" >> { def spec(tpe: String, rowList: RowList[_]) = tpe in withQueryResult(rowList) { implicit con => - SQL("SELECT v").as(scalar[Dummy].single) must throwA[Exception].like { case NonFatal(cause) => - cause must_=== AnormException(TypeDoesNotMatch(s"Column '.null' expected to be String; Found $tpe").message) + SQL("SELECT v").as(scalar[Dummy].single) must throwA[Exception].like { + case NonFatal(cause) => + cause must_=== AnormException( + TypeDoesNotMatch(s"Column '.null' expected to be String; Found $tpe").message + ) } } - spec("float", RowLists.floatList :+ 0.1f) + spec("float", RowLists.floatList :+ 0.1F) spec("int", RowLists.intList :+ 1) } } @@ -79,7 +83,7 @@ final class EnumColumnSpec extends org.specs2.mutable.Specification { } } - spec("float", RowLists.floatList :+ 0.1f) + spec("float", RowLists.floatList :+ 0.1F) spec("int", RowLists.intList :+ 1) } } @@ -121,7 +125,7 @@ final class EnumColumnSpec extends org.specs2.mutable.Specification { } } - spec("float", RowLists.floatList :+ 0.1f) + spec("float", RowLists.floatList :+ 0.1F) spec("int", RowLists.intList :+ 1) } } @@ -163,7 +167,7 @@ final class EnumColumnSpec extends org.specs2.mutable.Specification { } } - spec("float", RowLists.floatList :+ 0.1f) + spec("float", RowLists.floatList :+ 0.1F) spec("int", RowLists.intList :+ 1) } } diff --git a/enumeratum/src/test/scala/values/ValueEnumColumnSpec.scala b/enumeratum/src/test/scala/values/ValueEnumColumnSpec.scala index a4cba5cb..739b7de6 100644 --- a/enumeratum/src/test/scala/values/ValueEnumColumnSpec.scala +++ b/enumeratum/src/test/scala/values/ValueEnumColumnSpec.scala @@ -28,8 +28,9 @@ final class ValueEnumColumnSpec extends org.specs2.mutable.Specification { "not be parsed as Column from invalid Short representation" in { withQueryResult(RowLists.shortList :+ 0.toShort) { implicit con => - SQL("SELECT v").as(scalar[Drink].single) must throwA[Exception].like { case NonFatal(cause) => - cause mustEqual AnormException(TypeDoesNotMatch(s"Invalid value: 0").message) + SQL("SELECT v").as(scalar[Drink].single) must throwA[Exception].like { + case NonFatal(cause) => + cause mustEqual AnormException(TypeDoesNotMatch(s"Invalid value: 0").message) } } } @@ -40,7 +41,7 @@ final class ValueEnumColumnSpec extends org.specs2.mutable.Specification { SQL("SELECT v").as(scalar[Drink].single) must throwA[AnormException] } - spec("float", RowLists.floatList :+ 0.12f) + spec("float", RowLists.floatList :+ 0.12F) spec("String", RowLists.stringList :+ "foo") } } diff --git a/postgres/src/main/scala/package.scala b/postgres/src/main/scala/package.scala index ef490756..8c2e447c 100644 --- a/postgres/src/main/scala/package.scala +++ b/postgres/src/main/scala/package.scala @@ -18,12 +18,15 @@ sealed trait PGJson { // Could be moved to a separate module /** Allows to pass a `JsValue` as parameter to be stored as `PGobject`. */ - implicit def jsValueToStatement[J <: JsValue] = ToStatement[J] { (s, i, js) => - val pgObject = new PGobject() - pgObject.setType(JsValueParameterMetaData.sqlType) - pgObject.setValue(Json.stringify(js)) - s.setObject(i, pgObject, JsValueParameterMetaData.jdbcType) - } + implicit def jsValueToStatement[J <: JsValue]: ToStatement[J] = + ToStatement[J] { (s, i, js) => + val pgObject = new PGobject() + + pgObject.setType(JsValueParameterMetaData.sqlType) + pgObject.setValue(Json.stringify(js)) + + s.setObject(i, pgObject, JsValueParameterMetaData.jdbcType) + } implicit object JsObjectParameterMetaData extends ParameterMetaData[JsObject] { val sqlType = "JSONB" diff --git a/postgres/src/test/scala/ParameterMetaDataSpec.scala b/postgres/src/test/scala/ParameterMetaDataSpec.scala index 7d3f6af5..0369a43e 100644 --- a/postgres/src/test/scala/ParameterMetaDataSpec.scala +++ b/postgres/src/test/scala/ParameterMetaDataSpec.scala @@ -2,8 +2,8 @@ import play.api.libs.json.{ JsObject, JsValue } import anorm.ParameterMetaData -class ParameterMetaDataSpec extends org.specs2.mutable.Specification { - "Parameter metadata" title +final class ParameterMetaDataSpec extends org.specs2.mutable.Specification { + "Parameter metadata".title import anorm.postgresql._ diff --git a/postgres/src/test/scala/PostgreSQLSpec.scala b/postgres/src/test/scala/PostgreSQLSpec.scala index 647741c9..84bff900 100644 --- a/postgres/src/test/scala/PostgreSQLSpec.scala +++ b/postgres/src/test/scala/PostgreSQLSpec.scala @@ -19,8 +19,8 @@ import org.postgresql.util.PGobject import postgresql._ import AcolyteDSL.{ handleStatement, withQueryResult } -class PostgreSQLSpec extends org.specs2.mutable.Specification { - "PostgreSQL support" title +final class PostgreSQLSpec extends org.specs2.mutable.Specification { + "PostgreSQL support".title import acolyte.jdbc.Implicits._ @@ -38,39 +38,40 @@ class PostgreSQLSpec extends org.specs2.mutable.Specification { } def con = AcolyteDSL.connection(handleStatement.withUpdateHandler { - case UpdateExecution(ExpectedStmt, P(`id`) :: DefinedParameter(JsVal, JsDef) :: Nil) => { - + case UpdateExecution(ExpectedStmt, P(`id`) :: DefinedParameter(JsVal, JsDef) :: Nil) => 1 // update count - } + + case ex => + sys.error(s"Unexpected execution: $ex") }) f(con) } "when is an object" in { - withPgo("foo", """{"bar":1}""") { implicit con => + withPgo("foo", """{"bar":1}""") { implicit con: Connection => SQL"""INSERT INTO test(id, json) VALUES (${"foo"}, ${Json.obj("bar" -> 1)})""".executeUpdate() must_=== 1 } } "when is a string" in { - withPgo("foo", "\"bar\"") { implicit con => + withPgo("foo", "\"bar\"") { implicit con: Connection => SQL"""INSERT INTO test(id, json) VALUES (${"foo"}, ${Json.toJson("bar")})""".executeUpdate() must_=== 1 } } "when is a number" in { - withPgo("foo", "3") { implicit con => + withPgo("foo", "3") { implicit con: Connection => SQL"""INSERT INTO test(id, json) VALUES (${"foo"}, ${Json.toJson(3L)})""".executeUpdate() must_=== 1 } } "using JSON writer" >> { - "for pure value" in withPgo("foo", "2") { implicit con => + "for pure value" in withPgo("foo", "2") { implicit con: Connection => SQL"""INSERT INTO test(id, json) VALUES (${"foo"}, ${asJson[TestEnum](Lorem)})""".executeUpdate() must_=== 1 } - "for some optional value" in withPgo("foo", "2") { implicit con => + "for some optional value" in withPgo("foo", "2") { implicit con: Connection => SQL"""INSERT INTO test(id, json) VALUES (${"foo"}, ${asNullableJson[TestEnum](Some(Lorem))})""" .executeUpdate() must_=== 1 } @@ -78,11 +79,13 @@ class PostgreSQLSpec extends org.specs2.mutable.Specification { "for missing optional value" in { val id = "foo" - implicit def con = + implicit def con: Connection = AcolyteDSL.connection(handleStatement.withUpdateHandler { - case UpdateExecution(ExpectedStmt, P(`id`) :: DefinedParameter(null, JsDef) :: Nil) => { + case UpdateExecution(ExpectedStmt, P(`id`) :: DefinedParameter(null, JsDef) :: Nil) => 1 // update count - } + + case ex => + sys.error(s"Unexpected execution: $ex") }) SQL"""INSERT INTO test(id, json) VALUES (${id}, ${asNullableJson[TestEnum](Option.empty[TestEnum])})""" @@ -101,15 +104,15 @@ class PostgreSQLSpec extends org.specs2.mutable.Specification { } val jsVal = Json.obj("bar" -> 1) - "successfully" in withQueryResult(table :+ jsonb) { implicit con => + "successfully" in withQueryResult(table :+ jsonb) { implicit con: Connection => SQL"SELECT json FROM test".as(SqlParser.scalar[JsValue].single) must_=== jsVal } - "successfully as JsObject" in withQueryResult(table :+ jsonb) { implicit con => + "successfully as JsObject" in withQueryResult(table :+ jsonb) { implicit con: Connection => SQL"SELECT json FROM test".as(SqlParser.scalar[JsObject].single) must_=== jsVal } - "successfully using a Reads" in withQueryResult(table :+ jsonb) { implicit con => + "successfully using a Reads" in withQueryResult(table :+ jsonb) { implicit con: Connection => SQL"SELECT json FROM test".as(SqlParser.scalar(fromJson[TestEnum]).single) must_=== Bar } } @@ -117,7 +120,7 @@ class PostgreSQLSpec extends org.specs2.mutable.Specification { "UUID" should { "be passed as PostgreSQL UUID" in { - implicit val con = AcolyteDSL.connection(handleStatement.withUpdateHandler { + implicit val con: Connection = AcolyteDSL.connection(handleStatement.withUpdateHandler { case UpdateExecution( "INSERT INTO test_seq VALUES(?::UUID)", DefinedParameter(uuid: String, ParameterMetaData.Str) :: Nil diff --git a/project/Common.scala b/project/Common.scala index 73f79abd..e53aaa9b 100644 --- a/project/Common.scala +++ b/project/Common.scala @@ -31,18 +31,15 @@ object Common extends AutoPlugin { ("com.github.ghik" %% "silencer-plugin" % silencerVersion) .cross(CrossVersion.full) ), - ("com.github.ghik" %% "silencer-lib" % silencerVersion % Provided) - .cross(CrossVersion.full) + ("com.github.ghik" %% "silencer-lib" % silencerVersion % Provided) + .cross(CrossVersion.full) ) } else Seq.empty }, scalacOptions ++= Seq("-Xfatal-warnings"), scalacOptions ++= { if (scalaBinaryVersion.value != "3") { - Seq( - "-target:jvm-1.8", - "-Xlint", - "-g:vars") + Seq("-target:jvm-1.8", "-Xlint", "-g:vars") } else { Seq.empty } @@ -65,9 +62,7 @@ object Common extends AutoPlugin { "-Ywarn-macros:after" ) } else if (v == "2.11") { - Seq( - "-Xmax-classfile-name", "128", - "-Yopt:_", "-Ydead-code", "-Yclosure-elim", "-Yconst-opt") + Seq("-Xmax-classfile-name", "128", "-Yopt:_", "-Ydead-code", "-Yclosure-elim", "-Yconst-opt") } else { Seq( "-explaintypes", @@ -108,13 +103,16 @@ object Common extends AutoPlugin { def scalaUnmanaged(ver: String, base: File): Seq[File] = CrossVersion.partialVersion(ver) match { + case Some((2, 11)) => + Seq(base / "scala-2.12-", base / "scala-2.13-") + case Some((2, 12)) => Seq(base / "scala-2.12+", base / "scala-2.13-") case Some((3, _) | (2, 13)) => Seq(base / "scala-2.12+", base / "scala-2.13+") - case Some((_, minor)) => + case Some((_, minor)) => Seq(base / s"scala-2.${minor}-") case _ => diff --git a/project/plugins.sbt b/project/plugins.sbt index 1d648947..cf1c0ad3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -11,7 +11,7 @@ addSbtPlugin("com.github.sbt" % "sbt-unidoc" % "0.5.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0") -//TODO:addSbtPlugin("cchantep" % "sbt-scaladoc-compiler" % "0.2") +addSbtPlugin("cchantep" % "sbt-scaladoc-compiler" % "0.2") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.1") diff --git a/tokenizer/src/main/scala-2/anorm/TokenizedStatement.scala b/tokenizer/src/main/scala-2/anorm/TokenizedStatement.scala index 011f189a..612fb870 100644 --- a/tokenizer/src/main/scala-2/anorm/TokenizedStatement.scala +++ b/tokenizer/src/main/scala-2/anorm/TokenizedStatement.scala @@ -90,8 +90,9 @@ private[anorm] object TokenizedStatement { val groups = (gs match { case TokenGroup(List(StringToken("")), None) :: tgs => tgs // trim end case _ => gs - }).collect { case TokenGroup(pr, pl) => - TokenGroup(pr.reverse, pl) + }).collect { + case TokenGroup(pr, pl) => + TokenGroup(pr.reverse, pl) }.reverse TokenizedStatement(groups, ns.reverse) -> m diff --git a/tokenizer/src/main/scala-3/anorm/TokenizedStatement.scala b/tokenizer/src/main/scala-3/anorm/TokenizedStatement.scala index dd4bcdb9..9e12dc0d 100644 --- a/tokenizer/src/main/scala-3/anorm/TokenizedStatement.scala +++ b/tokenizer/src/main/scala-3/anorm/TokenizedStatement.scala @@ -7,21 +7,24 @@ package anorm private[anorm] case class TokenizedStatement(tokens: Seq[TokenGroup], names: Seq[String]) private[anorm] object TokenizedStatement { - import scala.quoted.{Expr,FromExpr,Quotes,Type} + import scala.quoted.{ Expr, FromExpr, Quotes, Type } /** Returns empty tokenized statement. */ lazy val empty = TokenizedStatement(Nil, Nil) /** String interpolation to tokenize statement. */ - inline def stringInterpolation[T](inline parts: Seq[String], inline params: Seq[T & Show]): (TokenizedStatement, Map[String, T]) = ${tokenizeImpl[T]('parts, 'params)} + inline def stringInterpolation[T]( + inline parts: Seq[String], + inline params: Seq[T & Show] + ): (TokenizedStatement, Map[String, T]) = ${ tokenizeImpl[T]('parts, 'params) } /** Tokenization macro */ private def tokenizeImpl[T]( - parts: Expr[Seq[String]], - params: Expr[Seq[T & Show]] + parts: Expr[Seq[String]], + params: Expr[Seq[T & Show]] )(using Quotes, Type[T]): Expr[(TokenizedStatement, Map[String, T])] = '{ - val _parts = ${parts} - val _params = ${params} + val _parts = ${ parts } + val _params = ${ params } tokenize(Iterator[String](), Nil, _parts, _params, Nil, Nil, Map.empty[String, T]) } @@ -51,12 +54,12 @@ private[anorm] object TokenizedStatement { StringToken(str.dropRight(1)) :: gts } val ng = TokenGroup( - (tks ::: StringToken(v.show) :: - before), + tks ::: StringToken(v.show) :: + before, pl ) - tokenize(ti, tks.tail, parts, ps.tail, (ng :: groups), ns, m) + tokenize(ti, tks.tail, parts, ps.tail, ng :: groups, ns, m) case _ => val ng = TokenGroup(tks, None) @@ -66,8 +69,8 @@ private[anorm] object TokenizedStatement { tks.tail, parts, ps.tail, - (ng :: prev.copy(placeholder = Some(n)) :: groups), - (n +: ns), + ng :: prev.copy(placeholder = Some(n)) :: groups, + n +: ns, m + (n -> v) ) } @@ -89,8 +92,9 @@ private[anorm] object TokenizedStatement { val groups = (gs match { case TokenGroup(List(StringToken("")), None) :: tgs => tgs // trim end case _ => gs - }).collect { case TokenGroup(pr, pl) => - TokenGroup(pr.reverse, pl) + }).collect { + case TokenGroup(pr, pl) => + TokenGroup(pr.reverse, pl) }.reverse TokenizedStatement(groups, ns.reverse) -> m