Skip to content

Commit aa8356b

Browse files
dfakhritdinovDenys Fakhritdinov
andauthored
Add CodecXXX capable of updating map, list, set types in statement instead 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>
1 parent f10aca7 commit aa8356b

File tree

8 files changed

+273
-13
lines changed

8 files changed

+273
-13
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.evolutiongaming.scassandra
2+
3+
import com.datastax.driver.core.{GettableByIndexData, SettableData}
4+
5+
trait UpdateByIdx[-A] {
6+
7+
def apply[D <: GettableByIndexData & SettableData[D]](
8+
data: D,
9+
idx: Int,
10+
value: A
11+
): D
12+
13+
}
14+
15+
object UpdateByIdx {
16+
17+
def apply[A: UpdateByIdx]: UpdateByIdx[A] = implicitly
18+
19+
implicit def fromEncodeByIdx[A: EncodeByIdx]: UpdateByIdx[A] =
20+
new UpdateByIdx[A] {
21+
def apply[D <: GettableByIndexData & SettableData[D]](
22+
data: D,
23+
idx: Int,
24+
value: A
25+
): D = EncodeByIdx[A].apply(data, idx, value)
26+
}
27+
28+
implicit final class Syntax[A](val self: UpdateByIdx[A]) extends AnyVal {
29+
30+
def contramap[B](f: B => A): UpdateByIdx[B] = new UpdateByIdx[B] {
31+
def apply[D <: GettableByIndexData & SettableData[D]](
32+
data: D,
33+
idx: Int,
34+
value: B
35+
): D = self(data, idx, f(value))
36+
}
37+
38+
}
39+
40+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.evolutiongaming.scassandra
2+
3+
import com.datastax.driver.core.{GettableByNameData, SettableData}
4+
5+
trait UpdateByName[-A] {
6+
7+
def apply[D <: GettableByNameData & SettableData[D]](
8+
data: D,
9+
name: String,
10+
value: A
11+
): D
12+
13+
}
14+
15+
object UpdateByName {
16+
17+
def apply[A: UpdateByName]: UpdateByName[A] = implicitly
18+
19+
implicit def fromEncodeByName[A: EncodeByName]: UpdateByName[A] =
20+
new UpdateByName[A] {
21+
def apply[D <: GettableByNameData & SettableData[D]](
22+
data: D,
23+
name: String,
24+
value: A
25+
): D = EncodeByName[A].apply(data, name, value)
26+
}
27+
28+
implicit final class Syntax[A](val self: UpdateByName[A]) extends AnyVal {
29+
30+
def contramap[B](f: B => A): UpdateByName[B] = new UpdateByName[B] {
31+
def apply[D <: GettableByNameData & SettableData[D]](
32+
data: D,
33+
name: String,
34+
value: B
35+
): D = self(data, name, f(value))
36+
}
37+
38+
}
39+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.evolutiongaming.scassandra
2+
3+
import com.datastax.driver.core.{GettableData, SettableData}
4+
5+
trait UpdateRow[-A] {
6+
7+
def apply[D <: GettableData & SettableData[D]](
8+
data: D,
9+
value: A
10+
): D
11+
12+
}
13+
14+
object UpdateRow {
15+
16+
def apply[A: UpdateRow]: UpdateRow[A] = implicitly
17+
18+
implicit def fromEncodeRow[A: EncodeRow]: UpdateRow[A] =
19+
new UpdateRow[A] {
20+
def apply[D <: GettableData & SettableData[D]](
21+
data: D,
22+
value: A
23+
): D = EncodeRow[A].apply(data, value)
24+
}
25+
26+
implicit final class Syntax[A](val self: UpdateRow[A]) extends AnyVal {
27+
28+
def contramap[B](f: B => A): UpdateRow[B] = new UpdateRow[B] {
29+
def apply[D <: GettableData & SettableData[D]](
30+
data: D,
31+
value: B
32+
): D = self(data, f(value))
33+
}
34+
35+
}
36+
}

scassandra/src/main/scala/com/evolutiongaming/scassandra/syntax.scala

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ object syntax {
1616
val iterator = self.iterator()
1717
val fetch = FromGFuture[F].apply { self.fetchMoreResults() }.void
1818
val fetched = Sync[F].delay { self.isFullyFetched }
19-
val next = Sync[F].delay { List.fill(self.getAvailableWithoutFetching)(iterator.next()) }
19+
val next = Sync[F].delay {
20+
List.fill(self.getAvailableWithoutFetching)(iterator.next())
21+
}
2022

2123
new Stream[F, Row] {
2224

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

2527
l.tailRecM[F, Either[L, R]] { l =>
26-
2728
def apply(rows: List[Row]) = {
2829
for {
2930
result <- rows.foldWhileM(l)(f)
@@ -37,7 +38,8 @@ object syntax {
3738
fetching <- fetch.start
3839
result <- rows.foldWhileM(l)(f)
3940
result <- result match {
40-
case l: Left[L, R] => fetching.join as l.rightCast[Either[L, R]]
41+
case l: Left[L, R] =>
42+
fetching.join as l.rightCast[Either[L, R]]
4143
case r: Right[L, R] => r.leftCast[L].asRight[L].pure[F]
4244
}
4345
} yield result
@@ -54,9 +56,12 @@ object syntax {
5456
}
5557
}
5658

57-
implicit class ScassandraSettableDataOps[A <: SettableData[A]](val self: A) extends AnyVal {
59+
implicit class ScassandraSettableDataOps[A <: SettableData[A]](val self: A)
60+
extends AnyVal {
5861

59-
def encode[B](name: String, value: B)(implicit encode: EncodeByName[B]): A = {
62+
def encode[B](name: String, value: B)(implicit
63+
encode: EncodeByName[B]
64+
): A = {
6065
encode(self, name, value)
6166
}
6267

@@ -68,7 +73,9 @@ object syntax {
6873
encode(self, idx, value)
6974
}
7075

71-
def encodeSome[B](name: String, value: Option[B])(implicit encode: EncodeByName[B]): A = {
76+
def encodeSome[B](name: String, value: Option[B])(implicit
77+
encode: EncodeByName[B]
78+
): A = {
7279
value.fold(self)(encode(self, name, _))
7380
}
7481

@@ -77,8 +84,8 @@ object syntax {
7784
}
7885
}
7986

80-
81-
implicit class ScassandraGettableByNameDataOps(val self: GettableByNameData) extends AnyVal {
87+
implicit class ScassandraGettableByNameDataOps(val self: GettableByNameData)
88+
extends AnyVal {
8289

8390
def decode[A](name: String)(implicit decode: DecodeByName[A]): A = {
8491
decode(self, name)
@@ -89,23 +96,37 @@ object syntax {
8996
}
9097
}
9198

92-
93-
implicit class ScassandraGettableByIdxDataOps(val self: GettableByIndexData) extends AnyVal {
99+
implicit class ScassandraGettableByIdxDataOps(val self: GettableByIndexData)
100+
extends AnyVal {
94101

95102
def decodeAt[A](idx: Int)(implicit decode: DecodeByIdx[A]): A = {
96103
decode(self, idx)
97104
}
98105
}
99106

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

103-
104109
implicit class ScassandraStatementOps(val self: Statement) extends AnyVal {
105110

106111
def trace(enable: Boolean): Statement = {
107112
if (enable) self.enableTracing()
108113
else self.disableTracing()
109114
}
110115
}
116+
117+
implicit class ScassandraUpdateSyntax[D <: GettableData & SettableData[D]](val data: D) extends AnyVal {
118+
119+
def update[A](value: A)(implicit update: UpdateRow[A]): D = {
120+
update(data, value)
121+
}
122+
123+
def update[A](name: String, value: A)(implicit update: UpdateByName[A]): D = {
124+
update(data, name, value)
125+
}
126+
127+
def updateAt[A](idx: Int, value: A)(implicit update: UpdateByIdx[A]): D = {
128+
update(data, idx, value)
129+
}
130+
131+
}
111132
}

scassandra/src/test/scala/com/evolutiongaming/scassandra/SyntaxSpec.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,17 @@ class SyntaxSpec extends AnyWordSpec with Matchers {
2929
val data1 = data.encodeAt(0, "str")
3030
data1.byIdx.get(0) shouldEqual Some("str")
3131
}
32+
33+
"update by name" in {
34+
val data = DataMock()
35+
val data1 = data.update("name", "str")
36+
data1.byName.get("name") shouldEqual Some("str")
37+
}
38+
39+
"update by idx" in {
40+
val data = DataMock()
41+
val data1 = data.updateAt(0, "str")
42+
data1.byIdx.get(0) shouldEqual Some("str")
43+
}
3244
}
3345
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.evolutiongaming.scassandra
2+
3+
import java.time.{Instant, LocalDate => LocalDateJ}
4+
import java.time.temporal.ChronoUnit
5+
6+
import com.datastax.driver.core.{Duration, LocalDate}
7+
import com.evolutiongaming.scassandra.syntax._
8+
import org.scalatest.matchers.should.Matchers
9+
import org.scalatest.wordspec.AnyWordSpec
10+
11+
class UpdateByIdxSpec extends AnyWordSpec with Matchers {
12+
13+
def of[A](expected: A)(implicit
14+
q: UpdateByIdx[A],
15+
e: UpdateByIdx[Option[A]],
16+
r: DecodeByIdx[A],
17+
t: DecodeByIdx[Option[A]]
18+
) = { () =>
19+
{
20+
val data = DataMock()
21+
22+
data
23+
.updateAt[A](0, expected)
24+
.decodeAt[A](0) shouldEqual expected
25+
26+
data
27+
.updateAt[Option[A]](1, Some(expected))
28+
.decodeAt[Option[A]](1) shouldEqual Some(expected)
29+
30+
data
31+
.updateAt[Option[A]](2, None)
32+
.decodeAt[Option[A]](2) shouldEqual None
33+
}
34+
}
35+
36+
"CodecByIdx" should {
37+
38+
for {
39+
(name, test) <- List(
40+
("String", of("string")),
41+
("Int", of(0)),
42+
("Long", of(0L)),
43+
("BigDecimal", of(BigDecimal(0))),
44+
("Double", of(0d)),
45+
("Float", of(0f)),
46+
("Instant", of(Instant.now().truncatedTo(ChronoUnit.MILLIS))),
47+
("Set", of(Set("str"))),
48+
("Duration", of(Duration.newInstance(1, 1, 1))),
49+
("LocalDate", of(LocalDate.fromYearMonthDay(2019, 10, 4))),
50+
("LocalDateJ", of(LocalDateJ.of(2019, 10, 4)))
51+
)
52+
} {
53+
s"encode & decode $name" in test()
54+
}
55+
}
56+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.evolutiongaming.scassandra
2+
3+
import java.time.{Instant, LocalDate => LocalDateJ}
4+
import java.time.temporal.ChronoUnit
5+
6+
import com.datastax.driver.core.{Duration, LocalDate}
7+
import com.evolutiongaming.scassandra.syntax._
8+
import org.scalatest.matchers.should.Matchers
9+
import org.scalatest.wordspec.AnyWordSpec
10+
11+
class UpdateByNameSpec extends AnyWordSpec with Matchers {
12+
13+
def of[A](expected: A)(implicit
14+
a: UpdateByName[A],
15+
b: UpdateByName[Option[A]],
16+
c: DecodeByName[A],
17+
d: DecodeByName[Option[A]]
18+
) = { () =>
19+
{
20+
val data = DataMock()
21+
22+
data
23+
.update[A]("0", expected)
24+
.decode[A]("0") shouldEqual expected
25+
26+
data
27+
.update[Option[A]]("1", Some(expected))
28+
.decode[Option[A]]("1") shouldEqual Some(expected)
29+
30+
data
31+
.update[Option[A]]("2", None)
32+
.decode[Option[A]]("2") shouldEqual None
33+
}
34+
}
35+
36+
"CodecByName" should {
37+
38+
for {
39+
(name, test) <- List(
40+
("String", of("string")),
41+
("Int", of(0)),
42+
("Long", of(0L)),
43+
("BigDecimal", of(BigDecimal(0))),
44+
("Double", of(0d)),
45+
("Float", of(0f)),
46+
("Instant", of(Instant.now().truncatedTo(ChronoUnit.MILLIS))),
47+
("Set", of(Set("str"))),
48+
("Duration", of(Duration.newInstance(1, 1, 1))),
49+
("LocalDate", of(LocalDate.fromYearMonthDay(2019, 10, 4))),
50+
("LocalDateJ", of(LocalDateJ.of(2019, 10, 4)))
51+
)
52+
} {
53+
s"encode & decode $name" in test()
54+
}
55+
}
56+
}

version.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ThisBuild / version := "5.2.2-SNAPSHOT"
1+
ThisBuild / version := "5.3.0"

0 commit comments

Comments
 (0)