From dd315f807ae409b7ba236a4a4b8e1046a70e3f18 Mon Sep 17 00:00:00 2001 From: Matt Hicks Date: Mon, 23 Dec 2024 08:25:41 -0600 Subject: [PATCH] Added Timestamp to provide a better type-safe reference to a date --- build.sbt | 2 +- core/src/main/scala/lightdb/Timestamp.scala | 15 +++++++++++++++ .../main/scala/lightdb/doc/RecordDocument.scala | 6 ++++-- .../scala/lightdb/doc/RecordDocumentModel.scala | 6 +++--- core/src/main/scala/lightdb/package.scala | 17 +++++++++++++++++ .../scala/lightdb/store/split/SplitStore.scala | 1 + .../scala/spec/AbstractSpecialCasesSpec.scala | 12 +++++++++--- 7 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 core/src/main/scala/lightdb/Timestamp.scala create mode 100644 core/src/main/scala/lightdb/package.scala diff --git a/build.sbt b/build.sbt index 50ea2260..e7b5c9c4 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ val developerURL: String = "https://matthicks.com" name := projectName ThisBuild / organization := org -ThisBuild / version := "1.2.3-SNAPSHOT" +ThisBuild / version := "1.3.0-SNAPSHOT" ThisBuild / scalaVersion := scala3 ThisBuild / crossScalaVersions := allScalaVersions ThisBuild / scalacOptions ++= Seq("-unchecked", "-deprecation") diff --git a/core/src/main/scala/lightdb/Timestamp.scala b/core/src/main/scala/lightdb/Timestamp.scala new file mode 100644 index 00000000..2538cb3d --- /dev/null +++ b/core/src/main/scala/lightdb/Timestamp.scala @@ -0,0 +1,15 @@ +package lightdb + +import fabric.define.DefType +import fabric.rw._ + +case class Timestamp(value: Long = System.currentTimeMillis()) extends AnyVal + +object Timestamp { + implicit val rw: RW[Timestamp] = RW.from( + r = _.value.json, + w = j => Timestamp(j.asLong), + d = DefType.Int + ) + implicit val numeric: Numeric[Timestamp] = Numeric[Long].map(Timestamp.apply)(_.value) +} \ No newline at end of file diff --git a/core/src/main/scala/lightdb/doc/RecordDocument.scala b/core/src/main/scala/lightdb/doc/RecordDocument.scala index a3720d70..e97fc42a 100644 --- a/core/src/main/scala/lightdb/doc/RecordDocument.scala +++ b/core/src/main/scala/lightdb/doc/RecordDocument.scala @@ -1,6 +1,8 @@ package lightdb.doc +import lightdb.Timestamp + trait RecordDocument[Doc <: RecordDocument[Doc]] extends Document[Doc] { - def created: Long - def modified: Long + def created: Timestamp + def modified: Timestamp } diff --git a/core/src/main/scala/lightdb/doc/RecordDocumentModel.scala b/core/src/main/scala/lightdb/doc/RecordDocumentModel.scala index b353bf49..c70280ff 100644 --- a/core/src/main/scala/lightdb/doc/RecordDocumentModel.scala +++ b/core/src/main/scala/lightdb/doc/RecordDocumentModel.scala @@ -1,9 +1,9 @@ package lightdb.doc import fabric.rw._ -import lightdb.field.Field +import lightdb.Timestamp trait RecordDocumentModel[Doc <: RecordDocument[Doc]] extends DocumentModel[Doc] { - val created: I[Long] = field.index("created", (doc: Doc) => doc.created) - val modified: I[Long] = field.index("modified", (doc: Doc) => doc.modified) + val created: I[Timestamp] = field.index("created", (doc: Doc) => doc.created) + val modified: I[Timestamp] = field.index("modified", (doc: Doc) => doc.modified) } \ No newline at end of file diff --git a/core/src/main/scala/lightdb/package.scala b/core/src/main/scala/lightdb/package.scala new file mode 100644 index 00000000..43f9684f --- /dev/null +++ b/core/src/main/scala/lightdb/package.scala @@ -0,0 +1,17 @@ +package object lightdb { + implicit class NumericOps[A](numeric: Numeric[A]) { + def map[B](to: A => B)(from: B => A): Numeric[B] = new Numeric[B] { + override def plus(x: B, y: B): B = to(numeric.plus(from(x), from(y))) + override def minus(x: B, y: B): B = to(numeric.minus(from(x), from(y))) + override def times(x: B, y: B): B = to(numeric.times(from(x), from(y))) + override def negate(x: B): B = to(numeric.negate(from(x))) + override def fromInt(x: Int): B = to(numeric.fromInt(x)) + override def toInt(x: B): Int = numeric.toInt(from(x)) + override def toLong(x: B): Long = numeric.toLong(from(x)) + override def toFloat(x: B): Float = numeric.toFloat(from(x)) + override def toDouble(x: B): Double = numeric.toDouble(from(x)) + override def compare(x: B, y: B): Int = numeric.compare(from(x), from(y)) + override def parseString(str: String): Option[B] = numeric.parseString(str).map(to) + } + } +} \ No newline at end of file diff --git a/core/src/main/scala/lightdb/store/split/SplitStore.scala b/core/src/main/scala/lightdb/store/split/SplitStore.scala index 56c4a3ef..b2695d3b 100644 --- a/core/src/main/scala/lightdb/store/split/SplitStore.scala +++ b/core/src/main/scala/lightdb/store/split/SplitStore.scala @@ -97,6 +97,7 @@ case class SplitStore[Doc <: Document[Doc], Model <: DocumentModel[Doc]](overrid } private def reIndexInternal()(implicit transaction: Transaction[Doc]): Unit = { + // TODO: Process concurrently searching.truncate() storage.iterator.foreach { doc => searching.insert(doc) diff --git a/core/src/test/scala/spec/AbstractSpecialCasesSpec.scala b/core/src/test/scala/spec/AbstractSpecialCasesSpec.scala index 8e455a04..183cb9ae 100644 --- a/core/src/test/scala/spec/AbstractSpecialCasesSpec.scala +++ b/core/src/test/scala/spec/AbstractSpecialCasesSpec.scala @@ -6,7 +6,7 @@ import lightdb.collection.Collection import lightdb.doc.{JsonConversion, RecordDocument, RecordDocumentModel} import lightdb.store.StoreManager import lightdb.upgrade.DatabaseUpgrade -import lightdb.{Id, LightDB, Sort} +import lightdb.{Id, LightDB, Sort, Timestamp} import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec @@ -34,6 +34,12 @@ trait AbstractSpecialCasesSpec extends AnyWordSpec with Matchers { spec => list.map(_._id).toSet should be(Set(SpecialOne.id("first"), SpecialOne.id("second"))) } } + "verify filtering by created works" in { + DB.specialOne.transaction { implicit transaction => + DB.specialOne.query.filter(_.created < Timestamp()).toList.map(_.name).toSet should be(Set("First", "Second")) + DB.specialOne.query.filter(_.created > Timestamp()).toList.map(_.name).toSet should be(Set.empty) + } + } "verify the storage of data is correct" in { DB.specialOne.transaction { implicit transaction => val list = DB.specialOne.query.sort(Sort.ByField(SpecialOne.name).asc).search.json(ref => List(ref._id)).list @@ -68,8 +74,8 @@ trait AbstractSpecialCasesSpec extends AnyWordSpec with Matchers { spec => case class SpecialOne(name: String, wrappedString: WrappedString, person: Person, - created: Long = System.currentTimeMillis(), - modified: Long = System.currentTimeMillis(), + created: Timestamp = Timestamp(), + modified: Timestamp = Timestamp(), _id: Id[SpecialOne] = SpecialOne.id()) extends RecordDocument[SpecialOne] object SpecialOne extends RecordDocumentModel[SpecialOne] with JsonConversion[SpecialOne] {