Skip to content

Commit

Permalink
Add CodecXXX capable of updating map, list, set types in statement in…
Browse files Browse the repository at this point in the history
…stead of replacing it (#263)

* Add CodecXXX capable of updating map, list, set types in statement
instead of replacing it

* drop Codec and impl Update instead

---------

Co-authored-by: Denys Fakhritdinov <dfakhritdinov@evolution.com>
  • Loading branch information
dfakhritdinov and Denys Fakhritdinov authored Sep 9, 2024
1 parent f10aca7 commit aa8356b
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.evolutiongaming.scassandra

import com.datastax.driver.core.{GettableByIndexData, SettableData}

trait UpdateByIdx[-A] {

def apply[D <: GettableByIndexData & SettableData[D]](
data: D,
idx: Int,
value: A
): D

}

object UpdateByIdx {

def apply[A: UpdateByIdx]: UpdateByIdx[A] = implicitly

implicit def fromEncodeByIdx[A: EncodeByIdx]: UpdateByIdx[A] =
new UpdateByIdx[A] {
def apply[D <: GettableByIndexData & SettableData[D]](
data: D,
idx: Int,
value: A
): D = EncodeByIdx[A].apply(data, idx, value)
}

implicit final class Syntax[A](val self: UpdateByIdx[A]) extends AnyVal {

def contramap[B](f: B => A): UpdateByIdx[B] = new UpdateByIdx[B] {
def apply[D <: GettableByIndexData & SettableData[D]](
data: D,
idx: Int,
value: B
): D = self(data, idx, f(value))
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.evolutiongaming.scassandra

import com.datastax.driver.core.{GettableByNameData, SettableData}

trait UpdateByName[-A] {

def apply[D <: GettableByNameData & SettableData[D]](
data: D,
name: String,
value: A
): D

}

object UpdateByName {

def apply[A: UpdateByName]: UpdateByName[A] = implicitly

implicit def fromEncodeByName[A: EncodeByName]: UpdateByName[A] =
new UpdateByName[A] {
def apply[D <: GettableByNameData & SettableData[D]](
data: D,
name: String,
value: A
): D = EncodeByName[A].apply(data, name, value)
}

implicit final class Syntax[A](val self: UpdateByName[A]) extends AnyVal {

def contramap[B](f: B => A): UpdateByName[B] = new UpdateByName[B] {
def apply[D <: GettableByNameData & SettableData[D]](
data: D,
name: String,
value: B
): D = self(data, name, f(value))
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.evolutiongaming.scassandra

import com.datastax.driver.core.{GettableData, SettableData}

trait UpdateRow[-A] {

def apply[D <: GettableData & SettableData[D]](
data: D,
value: A
): D

}

object UpdateRow {

def apply[A: UpdateRow]: UpdateRow[A] = implicitly

implicit def fromEncodeRow[A: EncodeRow]: UpdateRow[A] =
new UpdateRow[A] {
def apply[D <: GettableData & SettableData[D]](
data: D,
value: A
): D = EncodeRow[A].apply(data, value)
}

implicit final class Syntax[A](val self: UpdateRow[A]) extends AnyVal {

def contramap[B](f: B => A): UpdateRow[B] = new UpdateRow[B] {
def apply[D <: GettableData & SettableData[D]](
data: D,
value: B
): D = self(data, f(value))
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ object syntax {
val iterator = self.iterator()
val fetch = FromGFuture[F].apply { self.fetchMoreResults() }.void
val fetched = Sync[F].delay { self.isFullyFetched }
val next = Sync[F].delay { List.fill(self.getAvailableWithoutFetching)(iterator.next()) }
val next = Sync[F].delay {
List.fill(self.getAvailableWithoutFetching)(iterator.next())
}

new Stream[F, Row] {

def foldWhileM[L, R](l: L)(f: (L, Row) => F[Either[L, R]]) = {

l.tailRecM[F, Either[L, R]] { l =>

def apply(rows: List[Row]) = {
for {
result <- rows.foldWhileM(l)(f)
Expand All @@ -37,7 +38,8 @@ object syntax {
fetching <- fetch.start
result <- rows.foldWhileM(l)(f)
result <- result match {
case l: Left[L, R] => fetching.join as l.rightCast[Either[L, R]]
case l: Left[L, R] =>
fetching.join as l.rightCast[Either[L, R]]
case r: Right[L, R] => r.leftCast[L].asRight[L].pure[F]
}
} yield result
Expand All @@ -54,9 +56,12 @@ object syntax {
}
}

implicit class ScassandraSettableDataOps[A <: SettableData[A]](val self: A) extends AnyVal {
implicit class ScassandraSettableDataOps[A <: SettableData[A]](val self: A)
extends AnyVal {

def encode[B](name: String, value: B)(implicit encode: EncodeByName[B]): A = {
def encode[B](name: String, value: B)(implicit
encode: EncodeByName[B]
): A = {
encode(self, name, value)
}

Expand All @@ -68,7 +73,9 @@ object syntax {
encode(self, idx, value)
}

def encodeSome[B](name: String, value: Option[B])(implicit encode: EncodeByName[B]): A = {
def encodeSome[B](name: String, value: Option[B])(implicit
encode: EncodeByName[B]
): A = {
value.fold(self)(encode(self, name, _))
}

Expand All @@ -77,8 +84,8 @@ object syntax {
}
}


implicit class ScassandraGettableByNameDataOps(val self: GettableByNameData) extends AnyVal {
implicit class ScassandraGettableByNameDataOps(val self: GettableByNameData)
extends AnyVal {

def decode[A](name: String)(implicit decode: DecodeByName[A]): A = {
decode(self, name)
Expand All @@ -89,23 +96,37 @@ object syntax {
}
}


implicit class ScassandraGettableByIdxDataOps(val self: GettableByIndexData) extends AnyVal {
implicit class ScassandraGettableByIdxDataOps(val self: GettableByIndexData)
extends AnyVal {

def decodeAt[A](idx: Int)(implicit decode: DecodeByIdx[A]): A = {
decode(self, idx)
}
}


implicit def toCqlOps[A](a: A): ToCql.Ops.IdOps[A] = new ToCql.Ops.IdOps(a)


implicit class ScassandraStatementOps(val self: Statement) extends AnyVal {

def trace(enable: Boolean): Statement = {
if (enable) self.enableTracing()
else self.disableTracing()
}
}

implicit class ScassandraUpdateSyntax[D <: GettableData & SettableData[D]](val data: D) extends AnyVal {

def update[A](value: A)(implicit update: UpdateRow[A]): D = {
update(data, value)
}

def update[A](name: String, value: A)(implicit update: UpdateByName[A]): D = {
update(data, name, value)
}

def updateAt[A](idx: Int, value: A)(implicit update: UpdateByIdx[A]): D = {
update(data, idx, value)
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,17 @@ class SyntaxSpec extends AnyWordSpec with Matchers {
val data1 = data.encodeAt(0, "str")
data1.byIdx.get(0) shouldEqual Some("str")
}

"update by name" in {
val data = DataMock()
val data1 = data.update("name", "str")
data1.byName.get("name") shouldEqual Some("str")
}

"update by idx" in {
val data = DataMock()
val data1 = data.updateAt(0, "str")
data1.byIdx.get(0) shouldEqual Some("str")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.evolutiongaming.scassandra

import java.time.{Instant, LocalDate => LocalDateJ}
import java.time.temporal.ChronoUnit

import com.datastax.driver.core.{Duration, LocalDate}
import com.evolutiongaming.scassandra.syntax._
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

class UpdateByIdxSpec extends AnyWordSpec with Matchers {

def of[A](expected: A)(implicit
q: UpdateByIdx[A],
e: UpdateByIdx[Option[A]],
r: DecodeByIdx[A],
t: DecodeByIdx[Option[A]]
) = { () =>
{
val data = DataMock()

data
.updateAt[A](0, expected)
.decodeAt[A](0) shouldEqual expected

data
.updateAt[Option[A]](1, Some(expected))
.decodeAt[Option[A]](1) shouldEqual Some(expected)

data
.updateAt[Option[A]](2, None)
.decodeAt[Option[A]](2) shouldEqual None
}
}

"CodecByIdx" should {

for {
(name, test) <- List(
("String", of("string")),
("Int", of(0)),
("Long", of(0L)),
("BigDecimal", of(BigDecimal(0))),
("Double", of(0d)),
("Float", of(0f)),
("Instant", of(Instant.now().truncatedTo(ChronoUnit.MILLIS))),
("Set", of(Set("str"))),
("Duration", of(Duration.newInstance(1, 1, 1))),
("LocalDate", of(LocalDate.fromYearMonthDay(2019, 10, 4))),
("LocalDateJ", of(LocalDateJ.of(2019, 10, 4)))
)
} {
s"encode & decode $name" in test()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.evolutiongaming.scassandra

import java.time.{Instant, LocalDate => LocalDateJ}
import java.time.temporal.ChronoUnit

import com.datastax.driver.core.{Duration, LocalDate}
import com.evolutiongaming.scassandra.syntax._
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

class UpdateByNameSpec extends AnyWordSpec with Matchers {

def of[A](expected: A)(implicit
a: UpdateByName[A],
b: UpdateByName[Option[A]],
c: DecodeByName[A],
d: DecodeByName[Option[A]]
) = { () =>
{
val data = DataMock()

data
.update[A]("0", expected)
.decode[A]("0") shouldEqual expected

data
.update[Option[A]]("1", Some(expected))
.decode[Option[A]]("1") shouldEqual Some(expected)

data
.update[Option[A]]("2", None)
.decode[Option[A]]("2") shouldEqual None
}
}

"CodecByName" should {

for {
(name, test) <- List(
("String", of("string")),
("Int", of(0)),
("Long", of(0L)),
("BigDecimal", of(BigDecimal(0))),
("Double", of(0d)),
("Float", of(0f)),
("Instant", of(Instant.now().truncatedTo(ChronoUnit.MILLIS))),
("Set", of(Set("str"))),
("Duration", of(Duration.newInstance(1, 1, 1))),
("LocalDate", of(LocalDate.fromYearMonthDay(2019, 10, 4))),
("LocalDateJ", of(LocalDateJ.of(2019, 10, 4)))
)
} {
s"encode & decode $name" in test()
}
}
}
2 changes: 1 addition & 1 deletion version.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ThisBuild / version := "5.2.2-SNAPSHOT"
ThisBuild / version := "5.3.0"

0 comments on commit aa8356b

Please sign in to comment.