diff --git a/src/main/scala/com/github/tminglei/slickpg/PgRangeSupport.scala b/src/main/scala/com/github/tminglei/slickpg/PgRangeSupport.scala index edd3d04c..bd8ad22b 100644 --- a/src/main/scala/com/github/tminglei/slickpg/PgRangeSupport.scala +++ b/src/main/scala/com/github/tminglei/slickpg/PgRangeSupport.scala @@ -11,26 +11,31 @@ case object `(_,_]` extends EdgeType case object `(_,_)` extends EdgeType case object `[_,_]` extends EdgeType -case class Range[T](start: T, end: T, edge: EdgeType = `[_,_)`) { +import PgRangeSupportUtils._ + +case class Range[T](start: Option[T], end: Option[T], edge: EdgeType) { def as[A](convert: (T => A)): Range[A] = { - new Range[A](convert(start), convert(end), edge) + new Range[A](start.map(convert), end.map(convert), edge) } override def toString = edge match { - case `[_,_)` => s"[$start,$end)" - case `(_,_]` => s"($start,$end]" - case `(_,_)` => s"($start,$end)" - case `[_,_]` => s"[$start,$end]" + case `[_,_)` => s"[${oToString(start)},${oToString(end)})" + case `(_,_]` => s"(${oToString(start)},${oToString(end)}]" + case `(_,_)` => s"(${oToString(start)},${oToString(end)})" + case `[_,_]` => s"[${oToString(start)},${oToString(end)}]" } } +object Range { + def apply[T](start: T, end: T, edge: EdgeType = `[_,_)`): Range[T] = Range(Some(start), Some(end), edge) +} + /** * simple range support; if all you want is just getting from / saving to db, and using pg range operations/methods, it should be enough */ trait PgRangeSupport extends range.PgRangeExtensions with utils.PgCommonJdbcTypes { driver: PostgresDriver => import driver.api._ - import PgRangeSupportUtils._ private def toTimestamp(str: String) = Timestamp.valueOf(str) private def toSQLDate(str: String) = Date.valueOf(str) @@ -123,30 +128,38 @@ object PgRangeSupportUtils { val `(_,_)Range` = """\("?([^,"]*)"?,[ ]*"?([^,"]*)"?\)""".r // matches: (_,_) val `[_,_]Range` = """\["?([^,"]*)"?,[ ]*"?([^,"]*)"?\]""".r // matches: [_,_] - def mkRangeFn[T](convert: (String => T)): (String => Range[T]) = + def mkRangeFn[T](convert: (String => T)): (String => Range[T]) = { + def conv[T](str: String, convert: (String => T)): Option[T] = + Option(str).filterNot(_.isEmpty).map(convert) + (str: String) => str match { - case `[_,_)Range`(start, end) => Range(convert(start), convert(end), `[_,_)`) - case `(_,_]Range`(start, end) => Range(convert(start), convert(end), `(_,_]`) - case `(_,_)Range`(start, end) => Range(convert(start), convert(end), `(_,_)`) - case `[_,_]Range`(start, end) => Range(convert(start), convert(end), `[_,_]`) + case `[_,_)Range`(start, end) => Range(conv(start, convert), conv(end, convert), `[_,_)`) + case `(_,_]Range`(start, end) => Range(conv(start, convert), conv(end, convert), `(_,_]`) + case `(_,_)Range`(start, end) => Range(conv(start, convert), conv(end, convert), `(_,_)`) + case `[_,_]Range`(start, end) => Range(conv(start, convert), conv(end, convert), `[_,_]`) } + } def toStringFn[T](toString: (T => String)): (Range[T] => String) = (r: Range[T]) => r.edge match { - case `[_,_)` => s"[${toString(r.start)},${toString(r.end)})" - case `(_,_]` => s"(${toString(r.start)},${toString(r.end)}]" - case `(_,_)` => s"(${toString(r.start)},${toString(r.end)})" - case `[_,_]` => s"[${toString(r.start)},${toString(r.end)}]" + case `[_,_)` => s"[${oToString(r.start, toString)},${oToString(r.end, toString)})" + case `(_,_]` => s"(${oToString(r.start, toString)},${oToString(r.end, toString)}]" + case `(_,_)` => s"(${oToString(r.start, toString)},${oToString(r.end, toString)})" + case `[_,_]` => s"[${oToString(r.start, toString)},${oToString(r.end, toString)}]" } /// def mkWithLength[T](start: T, length: Double, edge: EdgeType = `[_,_)`) = { val upper = (start.asInstanceOf[Double] + length).asInstanceOf[T] - new Range[T](start, upper, edge) + new Range[T](Some(start), Some(upper), edge) } def mkWithInterval[T <: java.util.Date](start: T, interval: Interval, edge: EdgeType = `[_,_)`) = { val end = (start +: interval).asInstanceOf[T] - new Range[T](start, end, edge) + new Range[T](Some(start), Some(end), edge) } + + ////// helper methods + private[slickpg] def oToString[T](o: Option[T], toString: (T => String) = (r: T) => r.toString) = + o.map(toString).getOrElse("") } diff --git a/src/test/scala/com/github/tminglei/slickpg/PgRangeSupportSuite.scala b/src/test/scala/com/github/tminglei/slickpg/PgRangeSupportSuite.scala index d5a27f37..e941c433 100644 --- a/src/test/scala/com/github/tminglei/slickpg/PgRangeSupportSuite.scala +++ b/src/test/scala/com/github/tminglei/slickpg/PgRangeSupportSuite.scala @@ -38,7 +38,7 @@ class PgRangeSupportSuite extends FunSuite { Some(Range(ts("2010-01-01 14:30:00"), ts("2010-01-03 15:30:00")))) val testRec2 = RangeBean(35L, Range(31, 59), Range(11.5f, 33.3f), Some(Range(ts("2011-01-01 14:30:00"), ts("2011-11-01 15:30:00")))) - val testRec3 = RangeBean(41L, Range(1, 5), Range(7.5f, 15.3f), None) + val testRec3 = RangeBean(41L, Range(1, 5), Range(Some(7.5f), None, `[_,_)`), None) test("Range Lifted support") { Await.result(db.run( @@ -48,6 +48,9 @@ class PgRangeSupportSuite extends FunSuite { RangeTests forceInsertAll List(testRec1, testRec2, testRec3) ).andThen( DBIO.seq( + RangeTests.sortBy(_.id).to[List].result.map( + r => assert(List(testRec1, testRec2, testRec3) === r) + ), // @> RangeTests.filter(_.tsRange @>^ ts("2011-10-01 15:30:00")).sortBy(_.id).to[List].result.map( r => assert(List(testRec2) === r)