@mreiche @dh
Hi!
I am working with @zoltan.zvara on this issue.
Here are my findings, after some debugging today:
The precision loss only happens when the data is inserted through the transaction API (confirmed with replace and insert as well) and it only happens with Couchbase 7.6.0.
Here is the code I used for testing:
package org.something.tests
import com.couchbase.client.scala.codec.{JsonDeserializer, JsonSerializer}
import com.couchbase.client.scala.durability.Durability
import com.couchbase.client.scala.env.ClusterEnvironment
import com.couchbase.client.scala.manager.bucket.{BucketType, CreateBucketSettings, EjectionMethod}
import com.couchbase.client.scala.manager.collection.CreateCollectionSettings
import com.couchbase.client.scala.transactions.TransactionAttemptContext
import org.json4s.jackson.Serialization.{read, write}
import org.json4s.{DefaultFormats, Formats}
import org.scalatest.flatspec.AnyFlatSpec
import java.nio.charset.Charset
import scala.reflect.classTag
final case class Data(field: BigDecimal)
class DataSerializer extends JsonDeserializer[Data] with JsonSerializer[Data] {
implicit val formats: Formats = DefaultFormats.withBigDecimal
def serialize(content: Data): scala.util.Try[scala.Array[scala.Byte]] =
scala.util.Try(
write[Data](content)(formats).getBytes(Charset.forName("UTF-8"))
)
def deserialize(bytes: scala.Array[scala.Byte]): scala.util.Try[Data] =
scala.util.Try(
read[Data](new String(bytes, Charset.forName("UTF-8")))(formats, implicitly[Manifest[Data]])
)
}
/**
* Results using Couchbase 7.2.4
* Serialized Data: [{"field":49000000000000000981}]
* Deserialized Data: [49000000000000000981]
* Insert: [Success({"field":49000000000000000981})]
* Transaction Insert: [Success({"field":49000000000000000981})]
*/
/**
* Results using Couchbase 7.6.0
* Serialized Data: [{"field":49000000000000000981}]
* Deserialized Data: [49000000000000000981]
* Insert: [Success({"field":49000000000000000981})]
* Transaction Insert: [Success({"field":4.9e+19})]
*/
/**
* Using:
* org.scala-lang:scala-library:2.13.13
* org.scala-lang:scala-reflect:2.13.13
*
* org.scalatest:scalatest:3.2.17
*
* org.json4s:json4s-native:3.7.0-M11
* org.json4s:json4s-jackson:3.7.0-M11
* org.json4s:json4s-ext:3.7.0-M11
* org.json4s:json4s-core:3.7.0-M11
* org.json4s:json4s-ast:3.7.0-M11
*
* com.couchbase.client:scala-client:1.6.0
*/
class suiteTest extends AnyFlatSpec {
"Test" should "be able to test" in {
// Setup
val serializer = new DataSerializer()
val key1 = "key-1"
val key2 = "key-2"
val data = Data(BigDecimal("49000000000000000981"))
// Serialization Proof
val serializedData = serializer.serialize(data).get
Console.println(s"Serialized Data: [${new String(serializedData, Charset.forName("UTF-8"))}]")
val deserializedData = serializer.deserialize(serializedData).get
Console.println(s"Deserialized Data: [${deserializedData.field}]")
// Cluster
val cluster = com.couchbase.client.scala.Cluster.connect(
"connectionString",
com.couchbase.client.scala.ClusterOptions.create("username", "password").environment(
ClusterEnvironment.builder.build.get
)
).get
// Bucket
cluster.buckets.create(
CreateBucketSettings(
name = "Test",
ramQuotaMB = 100,
flushEnabled = Some(true),
numReplicas = Some(0),
replicaIndexes = Some(false),
bucketType = Some(BucketType.Couchbase),
ejectionMethod = Some(EjectionMethod.ValueOnly),
maxTTL = None,
compressionMode = None,
conflictResolutionType = None,
minimumDurabilityLevel = Some(Durability.Majority)
)
).get
val bucket = cluster.bucket("Test")
// Scope
bucket.collections.createScope("Test").get
val scope = bucket.scope("Test")
// Collection
bucket.collections.createCollection("Test", "Test", CreateCollectionSettings()).get
val collection = scope.collection("Test")
// Insert
collection.insert[Data](
key1,
data
)(serializer).get
val getAfterInsertResult = collection.get(key1).get.contentAs[String](
JsonDeserializer.Passthrough.StringConvert,
classTag[String]
)
Console.println(s"Insert: [$getAfterInsertResult]")
// Transaction Insert
cluster.transactions.run((attemptContext: TransactionAttemptContext) =>
scala.util.Try {
attemptContext.insert[Data](
collection,
key2,
data
)(serializer).get
(): Unit
}
).get
val getAfterTransactionInsertResult = collection.get(key2).get.contentAs[String](
JsonDeserializer.Passthrough.StringConvert,
classTag[String]
)
Console.println(s"Transaction Insert: [$getAfterTransactionInsertResult]")
}
}
Here is the relevant part of the testing results:
/**
* Results using Couchbase 7.2.4
* Serialized Data: [{"field":49000000000000000981}]
* Deserialized Data: [49000000000000000981]
* Insert: [Success({"field":49000000000000000981})]
* Transaction Insert: [Success({"field":49000000000000000981})]
*/
/**
* Results using Couchbase 7.6.0
* Serialized Data: [{"field":49000000000000000981}]
* Deserialized Data: [49000000000000000981]
* Insert: [Success({"field":49000000000000000981})]
* Transaction Insert: [Success({"field":4.9e+19})]
*/
I have only changed the connection-string between the two test run.
- The serializer/deserializer works fine.
- Inserting through the normal API works correctly.
- Inserting through the transaction API works correctly with Couchbase 7.2.4
- Inserting through the transaction API DOES NOT work correctly with Couchbase 7.6.0