Store BigDecimal and BigInteger as Numbers like the Java SDK does. (#1933)

Also be able to read BigDecimal and BigInteger that were written as String.
Also does not lose precision by converting BigDecimal to double in the transcoder.

Closes #1611.
This commit is contained in:
Michael Reiche
2024-04-08 16:50:39 -07:00
committed by GitHub
parent fccd22a00b
commit 25aaac47e2
9 changed files with 142 additions and 58 deletions

View File

@@ -200,12 +200,12 @@ public class MappingCouchbaseConverterTests {
@Test
void writesBigInteger() {
CouchbaseDocument converted = new CouchbaseDocument();
BigIntegerEntity entity = new BigIntegerEntity(new BigInteger("12345"));
BigIntegerEntity entity = new BigIntegerEntity(new BigInteger("12345678901234567890123"));
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertThat(result.get("_class")).isEqualTo(entity.getClass().getName());
assertThat(result.get("attr0")).isEqualTo(entity.attr0.toString());
assertThat(result.get("attr0")).isEqualTo(entity.attr0);
assertThat(converted.getId()).isEqualTo(BaseEntity.ID);
}
@@ -213,21 +213,21 @@ public class MappingCouchbaseConverterTests {
void readsBigInteger() {
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", BigIntegerEntity.class.getName());
source.put("attr0", "12345");
source.put("attr0", new BigInteger("12345678901234567890123"));
BigIntegerEntity converted = converter.read(BigIntegerEntity.class, source);
assertThat(converted.attr0).isEqualTo(new BigInteger((String) source.get("attr0")));
assertThat(converted.attr0).isEqualTo(source.get("attr0"));
}
@Test
void writesBigDecimal() {
CouchbaseDocument converted = new CouchbaseDocument();
BigDecimalEntity entity = new BigDecimalEntity(new BigDecimal("123.45"));
BigDecimalEntity entity = new BigDecimalEntity(new BigDecimal("12345678901234567890123.45"));
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertThat(result.get("_class")).isEqualTo(entity.getClass().getName());
assertThat(result.get("attr0")).isEqualTo(entity.attr0.toString());
assertThat(result.get("attr0")).isEqualTo(entity.attr0);
assertThat(converted.getId()).isEqualTo(BaseEntity.ID);
}
@@ -235,10 +235,10 @@ public class MappingCouchbaseConverterTests {
void readsBigDecimal() {
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", BigDecimalEntity.class.getName());
source.put("attr0", "123.45");
source.put("attr0", new BigDecimal("12345678901234567890123.45"));
BigDecimalEntity converted = converter.read(BigDecimalEntity.class, source);
assertThat(converted.attr0).isEqualTo(new BigDecimal((String) source.get("attr0")));
assertThat(converted.attr0).isEqualTo(source.get("attr0"));
}
@Test

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2024 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.domain;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
@Document
/**
* @author Michael Reiche
*/
public class BigAirline extends Airline {
BigInteger airlineNumber = new BigInteger("88881234567890123456"); // less than 63 bits, otherwise query truncates
BigDecimal airlineDecimal = new BigDecimal("888812345678901.23"); // less than 53 bits in mantissa
@PersistenceConstructor
public BigAirline(String id, String name, String hqCountry, Number airlineNumber, Number airlineDecimal) {
super(id, name, hqCountry);
this.airlineNumber = airlineNumber != null && !airlineNumber.equals("")
? new BigInteger(airlineNumber.toString())
: this.airlineNumber;
this.airlineDecimal = airlineDecimal != null && !airlineDecimal.equals("")
? new BigDecimal(airlineDecimal.toString())
: this.airlineDecimal;
}
public BigInteger getAirlineNumber() {
return airlineNumber;
}
public BigDecimal getAirlineDecimal() {
return airlineDecimal;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2017-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.domain;
import java.util.List;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
/**
* @author Michael Reiche
*/
@Repository
public interface BigAirlineRepository extends CouchbaseRepository<BigAirline, String>,
QuerydslPredicateExecutor<BigAirline>, DynamicProxyable<BigAirlineRepository> {
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (name = $1)")
List<Airline> getByName(@Param("airline_name") String airlineName);
}

View File

@@ -33,15 +33,14 @@ import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.domain.Airline;
import org.springframework.data.couchbase.domain.AirlineRepository;
import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.BigAirline;
import org.springframework.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.Library;
import org.springframework.data.couchbase.domain.LibraryRepository;
import org.springframework.data.couchbase.domain.PersonValue;
@@ -59,9 +58,6 @@ import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.kv.GetResult;
/**
@@ -125,6 +121,17 @@ public class CouchbaseRepositoryKeyValueIntegrationTests extends ClusterAwareInt
airlineRepository.delete(airline);
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveBig() {
BigAirline airline = new BigAirline(UUID.randomUUID().toString(), "MyAirline", null, null, null);
airline = airlineRepository.save(airline);
Optional<Airline> foundMaybe = airlineRepository.findById(airline.getId());
BigAirline found = (BigAirline) foundMaybe.get();
assertEquals(found, airline);
airlineRepository.delete(airline);
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveAndFindById() {

View File

@@ -28,18 +28,21 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.AddressAnnotated;
import org.springframework.data.couchbase.domain.Airline;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.AirportRepository;
import org.springframework.data.couchbase.domain.AirportRepositoryAnnotated;
import org.springframework.data.couchbase.domain.BigAirline;
import org.springframework.data.couchbase.domain.BigAirlineRepository;
import org.springframework.data.couchbase.domain.ConfigScoped;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserCol;
@@ -72,6 +75,7 @@ public class CouchbaseRepositoryQueryCollectionIntegrationTests extends Collecti
@Autowired AirportRepositoryAnnotated airportRepositoryAnnotated;
@Autowired AirportRepository airportRepository;
@Autowired BigAirlineRepository bigAirlineRepository;
@Autowired UserColRepository userColRepository;
@Autowired UserSubmissionAnnotatedRepository userSubmissionAnnotatedRepository;
@Autowired UserSubmissionUnannotatedRepository userSubmissionUnannotatedRepository;
@@ -103,6 +107,7 @@ public class CouchbaseRepositoryQueryCollectionIntegrationTests extends Collecti
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(UserCol.class).inScope(otherScope).inCollection(otherCollection).all();
couchbaseTemplate.removeByQuery(Airport.class).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(BigAirline.class).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(Airport.class).inCollection(collectionName2).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
}
@@ -126,6 +131,19 @@ public class CouchbaseRepositoryQueryCollectionIntegrationTests extends Collecti
userColRepository.delete(found);
}
@Test
@Disabled // BigInteger and BigDecimal lose precision through Query
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveBig() {
BigAirline airline = new BigAirline(UUID.randomUUID().toString(), "MyAirline", null, null, null);
airline = bigAirlineRepository.withCollection(collectionName).save(airline);
List<Airline> foundMaybe = bigAirlineRepository.withCollection(collectionName)
.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).getByName("MyAirline");
BigAirline found = (BigAirline) foundMaybe.get(0);
assertEquals(found, airline);
bigAirlineRepository.withCollection(collectionName).delete(airline);
}
@Test
public void myTest() {

View File

@@ -39,16 +39,12 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbaseEventListener;
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
import org.springframework.data.couchbase.domain.Airline;
import org.springframework.data.couchbase.domain.AirlineRepository;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.QAirline;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
import org.springframework.data.couchbase.repository.auditing.EnableReactiveCouchbaseAuditing;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
@@ -63,9 +59,6 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;