From e0afeb366a7004a61fc5c8f341ce3e7d7ba57023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Jourdan-Weil?= Date: Fri, 15 May 2026 15:53:25 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20move=20Joda=20?= =?UTF-8?q?support=20in=20separate=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.sbt | 42 +++++- core/src/main/scala/anorm/Column.scala | 118 +--------------- .../main/scala/anorm/ToStatementMisc.scala | 75 +--------- joda/src/main/scala/anorm/JodaColumn.scala | 129 ++++++++++++++++++ .../main/scala/anorm/JodaToStatement.scala | 82 +++++++++++ 5 files changed, 251 insertions(+), 195 deletions(-) create mode 100644 joda/src/main/scala/anorm/JodaColumn.scala create mode 100644 joda/src/main/scala/anorm/JodaToStatement.scala diff --git a/build.sbt b/build.sbt index c5dfcdea..839e5fde 100644 --- a/build.sbt +++ b/build.sbt @@ -201,12 +201,30 @@ lazy val `anorm-core` = project coreMimaFilter, // was deprecated ProblemFilters.exclude[IncompatibleMethTypeProblem]("anorm.ColumnNotFound.copy"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("anorm.ColumnNotFound.copy$default$2") + ProblemFilters.exclude[IncompatibleResultTypeProblem]("anorm.ColumnNotFound.copy$default$2"), + // Joda-Time support moved to anorm-joda module + ProblemFilters.exclude[MissingClassProblem]("anorm.JodaColumn"), + ProblemFilters.exclude[MissingClassProblem]("anorm.JodaToStatement"), + ProblemFilters.exclude[MissingClassProblem]("anorm.JodaParameterMetaData"), + ProblemFilters.exclude[MissingClassProblem]("anorm.JodaParameterMetaData$"), + ProblemFilters.exclude[MissingClassProblem]("anorm.JodaParameterMetaData$JodaTimeMetaData"), + ProblemFilters.exclude[MissingClassProblem]("anorm.JodaParameterMetaData$JodaDateTimeMetaData$"), + ProblemFilters.exclude[MissingClassProblem]("anorm.JodaParameterMetaData$JodaLocalDateTimeMetaData$"), + ProblemFilters.exclude[MissingClassProblem]("anorm.JodaParameterMetaData$JodaInstantMetaData$"), + ProblemFilters.exclude[MissingClassProblem]("anorm.JodaParameterMetaData$JodaLocalDateMetaData$"), + ProblemFilters.exclude[MissingTypesProblem]("anorm.Column$"), + ProblemFilters.exclude[MissingTypesProblem]("anorm.ToStatement$"), + ProblemFilters.exclude[DirectMissingMethodProblem]("anorm.Column.columnToJodaLocalDate"), + ProblemFilters.exclude[DirectMissingMethodProblem]("anorm.Column.columnToJodaLocalDateTime"), + ProblemFilters.exclude[DirectMissingMethodProblem]("anorm.Column.columnToJodaDateTime"), + ProblemFilters.exclude[DirectMissingMethodProblem]("anorm.Column.columnToJodaInstant"), + ProblemFilters.exclude[DirectMissingMethodProblem]("anorm.ToStatement.jodaDateTimeToStatement"), + ProblemFilters.exclude[DirectMissingMethodProblem]("anorm.ToStatement.jodaLocalDateTimeToStatement"), + ProblemFilters.exclude[DirectMissingMethodProblem]("anorm.ToStatement.jodaLocalDateToStatement"), + ProblemFilters.exclude[DirectMissingMethodProblem]("anorm.ToStatement.jodaInstantToStatement") ), libraryDependencies ++= { Seq( - "joda-time" % "joda-time" % "2.14.2", - "org.joda" % "joda-convert" % "3.0.1", "org.scala-lang.modules" %% "scala-parser-combinators" % parserCombinatorsVer.value, "org.scala-lang.modules" %% "scala-xml" % xmlVer % Test, "com.h2database" % "h2" % "2.3.230" % Test, @@ -317,6 +335,21 @@ lazy val `anorm-enumeratum` = project ) .dependsOn(`anorm-core`) +lazy val `anorm-joda` = project + .in(file("joda")) + .settings( + Seq( + mimaPreviousArtifacts := Set.empty, + libraryDependencies ++= Seq( + "joda-time" % "joda-time" % "2.14.2", + "org.joda" % "joda-convert" % "3.0.1", + "com.h2database" % "h2" % "2.3.230" % Test, + acolyte + ) ++ specs2Test + ) ++ licensing + ) + .dependsOn(`anorm-core`) + // --- lazy val `anorm-parent` = project @@ -328,7 +361,8 @@ lazy val `anorm-parent` = project `anorm-akka`, `anorm-pekko`, `anorm-postgres`, - `anorm-enumeratum` + `anorm-enumeratum`, + `anorm-joda` ) .settings( Seq( diff --git a/core/src/main/scala/anorm/Column.scala b/core/src/main/scala/anorm/Column.scala index a12047e2..1760b33a 100644 --- a/core/src/main/scala/anorm/Column.scala +++ b/core/src/main/scala/anorm/Column.scala @@ -87,7 +87,7 @@ trait Column[A] extends ((Any, MetaDataItem) => Either[SqlRequestError, A]) { pa } /** Column companion, providing default conversions. */ -object Column extends JodaColumn with JavaTimeColumn { +object Column extends JavaTimeColumn { /** * Resolves the `Column` instance for the given type. @@ -643,122 +643,6 @@ object Column extends JodaColumn with JavaTimeColumn { implicitly[ClassTag[InputStream]] } -sealed trait JodaColumn { - import org.joda.time.{ DateTime, LocalDate, LocalDateTime, Instant } - import Column.{ nonNull, className, timestamp => Ts } - - /** - * Parses column as Joda local date. - * Time zone is the one of default JVM time zone - * (see `org.joda.time.DateTimeZone.getDefault`). - * - * {{{ - * import org.joda.time.LocalDate - * import anorm._, SqlParser.scalar - * - * def ld(implicit con: java.sql.Connection): LocalDate = - * SQL("SELECT last_mod FROM tbl").as(scalar[LocalDate].single) - * }}} - */ - implicit val columnToJodaLocalDate: Column[LocalDate] = - nonNull { (value, meta) => - val MetaDataItem(qualified, _, _) = meta - - value match { - case date: java.util.Date => Right(new LocalDate(date.getTime)) - case time: Long => Right(new LocalDate(time)) - case TimestampWrapper1(ts) => Ts(ts)(t => new LocalDate(t.getTime)) - case TimestampWrapper2(ts) => Ts(ts)(t => new LocalDate(t.getTime)) - case _ => - Left(TypeDoesNotMatch(s"Cannot convert $value: ${className(value)} to Joda LocalDate for column $qualified")) - } - } - - /** - * Parses column as Joda local date/time. - * Time zone is the one of default JVM time zone - * (see `org.joda.time.DateTimeZone.getDefault`). - * - * {{{ - * import org.joda.time.LocalDateTime - * import anorm._, SqlParser._ - * - * def ldt(implicit con: java.sql.Connection): LocalDateTime = - * SQL("SELECT last_mod FROM tbl").as(scalar[LocalDateTime].single) - * }}} - */ - implicit val columnToJodaLocalDateTime: Column[LocalDateTime] = - nonNull { (value, meta) => - val MetaDataItem(qualified, _, _) = meta - - value match { - case date: java.util.Date => Right(new LocalDateTime(date.getTime)) - case time: Long => Right(new LocalDateTime(time)) - case TimestampWrapper1(ts) => Ts(ts)(t => new LocalDateTime(t.getTime)) - case TimestampWrapper2(ts) => Ts(ts)(t => new LocalDateTime(t.getTime)) - case _ => - Left( - TypeDoesNotMatch(s"Cannot convert $value: ${className(value)} to Joda LocalDateTime for column $qualified") - ) - } - } - - /** - * Parses column as joda DateTime - * - * {{{ - * import org.joda.time.DateTime - * import anorm._, SqlParser._ - * - * def dt(implicit con: java.sql.Connection): DateTime = - * SQL("SELECT last_mod FROM tbl").as(scalar[DateTime].single) - * }}} - */ - implicit val columnToJodaDateTime: Column[DateTime] = - nonNull { (value, meta) => - val MetaDataItem(qualified, _, _) = meta - - @SuppressWarnings(Array("AsInstanceOf")) - def unsafe = value match { - case date: Date => Right(new DateTime(date.getTime)) - case time: Long => Right(new DateTime(time)) - case TimestampWrapper1(ts) => - Option(ts).fold(Right(null.asInstanceOf[DateTime]))(t => Right(new DateTime(t.getTime))) - - case TimestampWrapper2(ts) => - Option(ts).fold(Right(null.asInstanceOf[DateTime]))(t => Right(new DateTime(t.getTime))) - - case _ => - Left(TypeDoesNotMatch(s"Cannot convert $value: ${className(value)} to DateTime for column $qualified")) - } - - unsafe - } - - /** - * Parses column as joda Instant - * - * {{{ - * import anorm._, SqlParser.scalar - * import org.joda.time.Instant - * - * def d(implicit con: java.sql.Connection): Instant = - * SQL("SELECT last_mod FROM tbl").as(scalar[Instant].single) - * }}} - */ - implicit val columnToJodaInstant: Column[Instant] = - nonNull { (value, meta) => - val MetaDataItem(qualified, _, _) = meta - value match { - case date: Date => Right(new Instant(date.getTime)) - case time: Long => Right(new Instant(time)) - case TimestampWrapper1(ts) => Ts(ts)(t => new Instant(t.getTime)) - case TimestampWrapper2(ts) => Ts(ts)(t => new Instant(t.getTime)) - case _ => Left(TypeDoesNotMatch(s"Cannot convert $value: ${className(value)} to Instant for column $qualified")) - } - } -} - sealed trait JavaTimeColumn { import java.time.{ ZonedDateTime, ZoneOffset, ZoneId, LocalDate, LocalDateTime, Instant } import Column.{ nonNull, className, timestamp => Ts } diff --git a/core/src/main/scala/anorm/ToStatementMisc.scala b/core/src/main/scala/anorm/ToStatementMisc.scala index 344af42c..705a48d4 100644 --- a/core/src/main/scala/anorm/ToStatementMisc.scala +++ b/core/src/main/scala/anorm/ToStatementMisc.scala @@ -723,79 +723,6 @@ sealed trait ToStatementPriority0 { } } -/** Meta data for Joda parameters */ -object JodaParameterMetaData { - import org.joda.time.{ DateTime, LocalDate, LocalDateTime, Instant } - - import java.sql.Types - - sealed trait JodaTimeMetaData { - val sqlType = "TIMESTAMP" - val jdbcType = Types.TIMESTAMP - } - - /** Date/time parameter meta data */ - implicit object JodaDateTimeMetaData extends ParameterMetaData[DateTime] with JodaTimeMetaData - - /** Local date/time parameter meta data */ - implicit object JodaLocalDateTimeMetaData extends ParameterMetaData[LocalDateTime] with JodaTimeMetaData - - /** Instant parameter meta data */ - implicit object JodaInstantMetaData extends ParameterMetaData[Instant] with JodaTimeMetaData - - /** Local date parameter meta data */ - implicit object JodaLocalDateMetaData extends ParameterMetaData[LocalDate] with JodaTimeMetaData -} - -sealed trait JodaToStatement { - import org.joda.time.{ DateTime, LocalDate, LocalDateTime, Instant } - - /** - * Sets joda-time DateTime as statement parameter. - * For `null` value, `setNull` with `TIMESTAMP` is called on statement. - */ - implicit def jodaDateTimeToStatement(implicit meta: ParameterMetaData[DateTime]): ToStatement[DateTime] = - new ToStatement[DateTime] { - def set(s: PreparedStatement, index: Int, date: DateTime): Unit = - if (date != (null: DateTime)) { - s.setTimestamp(index, new Timestamp(date.getMillis())) - } else s.setNull(index, meta.jdbcType) - } - - /** - * Sets a local date/time on statement. - */ - implicit def jodaLocalDateTimeToStatement(implicit - meta: ParameterMetaData[LocalDateTime] - ): ToStatement[LocalDateTime] = new ToStatement[LocalDateTime] { - def set(s: PreparedStatement, i: Int, t: LocalDateTime): Unit = - if (t == (null: LocalDateTime)) s.setNull(i, meta.jdbcType) - else s.setTimestamp(i, new Timestamp(t.toDateTime.getMillis)) - } - - /** - * Sets a local date on statement. - */ - implicit def jodaLocalDateToStatement(implicit meta: ParameterMetaData[LocalDate]): ToStatement[LocalDate] = - new ToStatement[LocalDate] { - def set(s: PreparedStatement, i: Int, t: LocalDate): Unit = - if (t == (null: LocalDate)) s.setNull(i, meta.jdbcType) - else s.setTimestamp(i, new Timestamp(t.toDate.getTime)) - } - - /** - * Sets joda-time Instant as statement parameter. - * For `null` value, `setNull` with `TIMESTAMP` is called on statement. - */ - implicit def jodaInstantToStatement(implicit meta: ParameterMetaData[Instant]): ToStatement[Instant] = - new ToStatement[Instant] { - def set(s: PreparedStatement, index: Int, instant: Instant): Unit = - if (instant != (null: Instant)) { - s.setTimestamp(index, new Timestamp(instant.getMillis)) - } else s.setNull(index, meta.jdbcType) - } -} - sealed trait JavaTimeToStatement { import java.time.{ Instant, LocalDate, LocalDateTime, ZonedDateTime } @@ -894,7 +821,7 @@ sealed trait ToStatementPriority1 extends ToStatementPriority0 { /** * Provided conversions to set statement parameter. */ -private[anorm] class ToStatementConversions extends ToStatementPriority1 with JodaToStatement with JavaTimeToStatement { +private[anorm] class ToStatementConversions extends ToStatementPriority1 with JavaTimeToStatement { /** * Resolves `ToStatement` instance for the given type. diff --git a/joda/src/main/scala/anorm/JodaColumn.scala b/joda/src/main/scala/anorm/JodaColumn.scala new file mode 100644 index 00000000..a6801b7f --- /dev/null +++ b/joda/src/main/scala/anorm/JodaColumn.scala @@ -0,0 +1,129 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ + +package anorm + +import java.util.Date + +trait JodaColumn { + import org.joda.time.{ DateTime, Instant, LocalDate, LocalDateTime } + import Column.{ className, nonNull, timestamp => Ts } + + /** + * Parses column as Joda local date. + * Time zone is the one of default JVM time zone + * (see `org.joda.time.DateTimeZone.getDefault`). + * + * {{{ + * import org.joda.time.LocalDate + * import anorm._, SqlParser.scalar + * import anorm.JodaColumn._ + * + * def ld(implicit con: java.sql.Connection): LocalDate = + * SQL("SELECT last_mod FROM tbl").as(scalar[LocalDate].single) + * }}} + */ + implicit val columnToJodaLocalDate: Column[LocalDate] = + nonNull { (value, meta) => + val MetaDataItem(qualified, _, _) = meta + + value match { + case date: java.util.Date => Right(new LocalDate(date.getTime)) + case time: Long => Right(new LocalDate(time)) + case TimestampWrapper1(ts) => Ts(ts)(t => new LocalDate(t.getTime)) + case TimestampWrapper2(ts) => Ts(ts)(t => new LocalDate(t.getTime)) + case _ => + Left(TypeDoesNotMatch(s"Cannot convert $value: ${className(value)} to Joda LocalDate for column $qualified")) + } + } + + /** + * Parses column as Joda local date/time. + * Time zone is the one of default JVM time zone + * (see `org.joda.time.DateTimeZone.getDefault`). + * + * {{{ + * import org.joda.time.LocalDateTime + * import anorm._, SqlParser._ + * import anorm.JodaColumn._ + * + * def ldt(implicit con: java.sql.Connection): LocalDateTime = + * SQL("SELECT last_mod FROM tbl").as(scalar[LocalDateTime].single) + * }}} + */ + implicit val columnToJodaLocalDateTime: Column[LocalDateTime] = + nonNull { (value, meta) => + val MetaDataItem(qualified, _, _) = meta + + value match { + case date: java.util.Date => Right(new LocalDateTime(date.getTime)) + case time: Long => Right(new LocalDateTime(time)) + case TimestampWrapper1(ts) => Ts(ts)(t => new LocalDateTime(t.getTime)) + case TimestampWrapper2(ts) => Ts(ts)(t => new LocalDateTime(t.getTime)) + case _ => + Left( + TypeDoesNotMatch(s"Cannot convert $value: ${className(value)} to Joda LocalDateTime for column $qualified") + ) + } + } + + /** + * Parses column as joda DateTime + * + * {{{ + * import org.joda.time.DateTime + * import anorm._, SqlParser._ + * import anorm.JodaColumn._ + * + * def dt(implicit con: java.sql.Connection): DateTime = + * SQL("SELECT last_mod FROM tbl").as(scalar[DateTime].single) + * }}} + */ + implicit val columnToJodaDateTime: Column[DateTime] = + nonNull { (value, meta) => + val MetaDataItem(qualified, _, _) = meta + + @SuppressWarnings(Array("AsInstanceOf")) + def unsafe = value match { + case date: Date => Right(new DateTime(date.getTime)) + case time: Long => Right(new DateTime(time)) + case TimestampWrapper1(ts) => + Option(ts).fold(Right(null.asInstanceOf[DateTime]))(t => Right(new DateTime(t.getTime))) + + case TimestampWrapper2(ts) => + Option(ts).fold(Right(null.asInstanceOf[DateTime]))(t => Right(new DateTime(t.getTime))) + + case _ => + Left(TypeDoesNotMatch(s"Cannot convert $value: ${className(value)} to DateTime for column $qualified")) + } + + unsafe + } + + /** + * Parses column as joda Instant + * + * {{{ + * import anorm._, SqlParser.scalar + * import org.joda.time.Instant + * import anorm.JodaColumn._ + * + * def d(implicit con: java.sql.Connection): Instant = + * SQL("SELECT last_mod FROM tbl").as(scalar[Instant].single) + * }}} + */ + implicit val columnToJodaInstant: Column[Instant] = + nonNull { (value, meta) => + val MetaDataItem(qualified, _, _) = meta + value match { + case date: Date => Right(new Instant(date.getTime)) + case time: Long => Right(new Instant(time)) + case TimestampWrapper1(ts) => Ts(ts)(t => new Instant(t.getTime)) + case TimestampWrapper2(ts) => Ts(ts)(t => new Instant(t.getTime)) + case _ => Left(TypeDoesNotMatch(s"Cannot convert $value: ${className(value)} to Instant for column $qualified")) + } + } +} + +object JodaColumn extends JodaColumn diff --git a/joda/src/main/scala/anorm/JodaToStatement.scala b/joda/src/main/scala/anorm/JodaToStatement.scala new file mode 100644 index 00000000..29704219 --- /dev/null +++ b/joda/src/main/scala/anorm/JodaToStatement.scala @@ -0,0 +1,82 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ + +package anorm + +import java.sql.{ PreparedStatement, Timestamp } + +/** Meta data for Joda parameters */ +object JodaParameterMetaData { + import org.joda.time.{ DateTime, Instant, LocalDate, LocalDateTime } + + import java.sql.Types + + sealed trait JodaTimeMetaData { + val sqlType = "TIMESTAMP" + val jdbcType = Types.TIMESTAMP + } + + /** Date/time parameter meta data */ + implicit object JodaDateTimeMetaData extends ParameterMetaData[DateTime] with JodaTimeMetaData + + /** Local date/time parameter meta data */ + implicit object JodaLocalDateTimeMetaData extends ParameterMetaData[LocalDateTime] with JodaTimeMetaData + + /** Instant parameter meta data */ + implicit object JodaInstantMetaData extends ParameterMetaData[Instant] with JodaTimeMetaData + + /** Local date parameter meta data */ + implicit object JodaLocalDateMetaData extends ParameterMetaData[LocalDate] with JodaTimeMetaData +} + +trait JodaToStatement { + import org.joda.time.{ DateTime, Instant, LocalDate, LocalDateTime } + + /** + * Sets joda-time DateTime as statement parameter. + * For `null` value, `setNull` with `TIMESTAMP` is called on statement. + */ + implicit def jodaDateTimeToStatement(implicit meta: ParameterMetaData[DateTime]): ToStatement[DateTime] = + new ToStatement[DateTime] { + def set(s: PreparedStatement, index: Int, date: DateTime): Unit = + if (date != (null: DateTime)) { + s.setTimestamp(index, new Timestamp(date.getMillis())) + } else s.setNull(index, meta.jdbcType) + } + + /** + * Sets a local date/time on statement. + */ + implicit def jodaLocalDateTimeToStatement(implicit + meta: ParameterMetaData[LocalDateTime] + ): ToStatement[LocalDateTime] = new ToStatement[LocalDateTime] { + def set(s: PreparedStatement, i: Int, t: LocalDateTime): Unit = + if (t == (null: LocalDateTime)) s.setNull(i, meta.jdbcType) + else s.setTimestamp(i, new Timestamp(t.toDateTime.getMillis)) + } + + /** + * Sets a local date on statement. + */ + implicit def jodaLocalDateToStatement(implicit meta: ParameterMetaData[LocalDate]): ToStatement[LocalDate] = + new ToStatement[LocalDate] { + def set(s: PreparedStatement, i: Int, t: LocalDate): Unit = + if (t == (null: LocalDate)) s.setNull(i, meta.jdbcType) + else s.setTimestamp(i, new Timestamp(t.toDate.getTime)) + } + + /** + * Sets joda-time Instant as statement parameter. + * For `null` value, `setNull` with `TIMESTAMP` is called on statement. + */ + implicit def jodaInstantToStatement(implicit meta: ParameterMetaData[Instant]): ToStatement[Instant] = + new ToStatement[Instant] { + def set(s: PreparedStatement, index: Int, instant: Instant): Unit = + if (instant != (null: Instant)) { + s.setTimestamp(index, new Timestamp(instant.getMillis)) + } else s.setNull(index, meta.jdbcType) + } +} + +object JodaToStatement extends JodaToStatement