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:
@@ -24,30 +24,27 @@ import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.YearMonth;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.json.JsonValueModule;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseList;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.json.JsonValueModule;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
@@ -65,13 +62,11 @@ public final class OtherConverters {
|
||||
* @return the list of converters to register.
|
||||
*/
|
||||
public static Collection<Converter<?, ?>> getConvertersToRegister() {
|
||||
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
|
||||
List<Converter<?, ?>> converters = new ArrayList<>();
|
||||
|
||||
converters.add(UuidToString.INSTANCE);
|
||||
converters.add(StringToUuid.INSTANCE);
|
||||
converters.add(BigIntegerToString.INSTANCE);
|
||||
converters.add(StringToBigInteger.INSTANCE);
|
||||
converters.add(BigDecimalToString.INSTANCE);
|
||||
converters.add(StringToBigDecimal.INSTANCE);
|
||||
converters.add(ByteArrayToString.INSTANCE);
|
||||
converters.add(StringToByteArray.INSTANCE);
|
||||
@@ -114,16 +109,7 @@ public final class OtherConverters {
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum BigIntegerToString implements Converter<BigInteger, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(BigInteger source) {
|
||||
return source == null ? null : source.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// to support reading BigIntegers that were written as Strings (now discontinued)
|
||||
@ReadingConverter
|
||||
public enum StringToBigInteger implements Converter<String, BigInteger> {
|
||||
INSTANCE;
|
||||
@@ -134,16 +120,7 @@ public final class OtherConverters {
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum BigDecimalToString implements Converter<BigDecimal, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(BigDecimal source) {
|
||||
return source == null ? null : source.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// to support reading BigDecimals that were written as Strings (now discontinued)
|
||||
@ReadingConverter
|
||||
public enum StringToBigDecimal implements Converter<String, BigDecimal> {
|
||||
INSTANCE;
|
||||
@@ -160,7 +137,7 @@ public final class OtherConverters {
|
||||
|
||||
@Override
|
||||
public String convert(byte[] source) {
|
||||
return source == null ? null : Base64Utils.encodeToString(source);
|
||||
return source == null ? null : Base64.getEncoder().encodeToString(source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +147,7 @@ public final class OtherConverters {
|
||||
|
||||
@Override
|
||||
public byte[] convert(String source) {
|
||||
return source == null ? null : Base64Utils.decode(source.getBytes(StandardCharsets.UTF_8));
|
||||
return source == null ? null : Base64.getDecoder().decode(source.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ public class JacksonTranslationService implements TranslationService, Initializi
|
||||
case VALUE_NUMBER_INT:
|
||||
return parser.getNumberValue();
|
||||
case VALUE_NUMBER_FLOAT:
|
||||
return parser.getDoubleValue();
|
||||
return parser.getDecimalValue();
|
||||
case VALUE_NULL:
|
||||
return null;
|
||||
default:
|
||||
|
||||
@@ -38,7 +38,7 @@ public abstract class CouchbaseSimpleTypes {
|
||||
Stream.of(JsonObject.class, JsonArray.class, Number.class).collect(toSet()), true);
|
||||
|
||||
public static final SimpleTypeHolder DOCUMENT_TYPES = new SimpleTypeHolder(
|
||||
Stream.of(CouchbaseDocument.class, CouchbaseList.class).collect(toSet()), true);
|
||||
Stream.of(CouchbaseDocument.class, CouchbaseList.class, Number.class).collect(toSet()), true);
|
||||
|
||||
private CouchbaseSimpleTypes() {}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user