From a6eea6c657c1b7ff3b4a1408f38423202cc5c094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Thu, 31 Oct 2024 14:01:28 +0100 Subject: [PATCH 1/3] fix: external invalid numbers should throw EvitaInvalidUsageException (cherry picked from commit 97941fb0650b9e197879d580292ec174dfdf94a0) --- .../java/io/evitadb/utils/NumberUtils.java | 22 ++++++++++++++++++- .../price/predicate/PricePredicate.java | 6 ++--- .../SellingPriceAvailableBitmapFilter.java | 16 +++++++------- .../mutation/index/PriceIndexMutator.java | 16 +++++++------- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/evita_common/src/main/java/io/evitadb/utils/NumberUtils.java b/evita_common/src/main/java/io/evitadb/utils/NumberUtils.java index b34aaa0cea..fa310e2229 100644 --- a/evita_common/src/main/java/io/evitadb/utils/NumberUtils.java +++ b/evita_common/src/main/java/io/evitadb/utils/NumberUtils.java @@ -23,6 +23,8 @@ package io.evitadb.utils; +import io.evitadb.exception.EvitaInvalidUsageException; + import javax.annotation.Nonnull; import java.math.BigDecimal; @@ -54,6 +56,7 @@ public static boolean isIntConvertibleNumber(@Nonnull Class parameterType) { * converted to the same type and applied. Method checks that there is no loss of precision during sum. */ @SuppressWarnings("RedundantCast") + @Nonnull public static Number sum(@Nonnull Number a, @Nonnull Number b) { if (a instanceof Byte) { final long longResult = convertToLong(a) + convertToLong(b); @@ -152,13 +155,29 @@ public static int convertToInt(@Nonnull BigDecimal number, int acceptDecimalPlac try { return number.stripTrailingZeros().scaleByPowerOfTen(acceptDecimalPlaces).intValueExact(); } catch (ArithmeticException ex) { - throw new IllegalArgumentException( + throw new ArithmeticException( "Cannot convert big decimal " + number + " to exact integer by using " + acceptDecimalPlaces + " decimal places!" ); } } + /** + * Converts passed {@link BigDecimal} number to integer value with rounding and overflow handling. + * + * @param number number to convert + * @param indexedPricePlaces number of decimal places to keep in the integer value + * @return converted integer value + * @throws EvitaInvalidUsageException if the number is too large to be converted to integer + */ + public static int convertExternalNumberToInt(@Nonnull BigDecimal number, int indexedPricePlaces) { + try { + return convertToInt(number, indexedPricePlaces); + } catch (ArithmeticException ex) { + throw new EvitaInvalidUsageException(ex.getMessage(), ex); + } + } + /** * Converts unknown number to {@link long}. */ @@ -173,6 +192,7 @@ public static long convertToLong(@Nonnull Number number) { /** * Converts unknown number to {@link BigDecimal}. */ + @Nonnull public static BigDecimal convertToBigDecimal(@Nonnull Number number) { if (number instanceof Byte) { return new BigDecimal(number.toString()); diff --git a/evita_engine/src/main/java/io/evitadb/core/query/algebra/price/predicate/PricePredicate.java b/evita_engine/src/main/java/io/evitadb/core/query/algebra/price/predicate/PricePredicate.java index 5f567ee2e9..4a824fbadb 100644 --- a/evita_engine/src/main/java/io/evitadb/core/query/algebra/price/predicate/PricePredicate.java +++ b/evita_engine/src/main/java/io/evitadb/core/query/algebra/price/predicate/PricePredicate.java @@ -78,8 +78,8 @@ protected PricePredicate( int indexedPricePlaces ) { this.queryPriceMode = queryPriceMode; - this.fromAsInt = from == null ? Integer.MIN_VALUE : NumberUtils.convertToInt(from, indexedPricePlaces); - this.toAsInt = to == null ? Integer.MAX_VALUE : NumberUtils.convertToInt(to, indexedPricePlaces); + this.fromAsInt = from == null ? Integer.MIN_VALUE : NumberUtils.convertExternalNumberToInt(from, indexedPricePlaces); + this.toAsInt = to == null ? Integer.MAX_VALUE : NumberUtils.convertExternalNumberToInt(to, indexedPricePlaces); this.indexedPricePlaces = indexedPricePlaces; if (queryPriceMode == null) { this.description = "NO FILTER PREDICATE"; @@ -109,7 +109,7 @@ protected PricePredicate( this.amountPredicate = new PriceAmountPredicate( queryPriceMode, from, to, indexedPricePlaces, amount -> { - final int amountAsInt = NumberUtils.convertToInt(amount, indexedPricePlaces); + final int amountAsInt = NumberUtils.convertExternalNumberToInt(amount, indexedPricePlaces); return amountAsInt >= fromAsInt && amountAsInt <= toAsInt; } ); diff --git a/evita_engine/src/main/java/io/evitadb/core/query/filter/translator/price/alternative/SellingPriceAvailableBitmapFilter.java b/evita_engine/src/main/java/io/evitadb/core/query/filter/translator/price/alternative/SellingPriceAvailableBitmapFilter.java index ee7005930c..c7ce69aa1d 100644 --- a/evita_engine/src/main/java/io/evitadb/core/query/filter/translator/price/alternative/SellingPriceAvailableBitmapFilter.java +++ b/evita_engine/src/main/java/io/evitadb/core/query/filter/translator/price/alternative/SellingPriceAvailableBitmapFilter.java @@ -127,8 +127,8 @@ public SellingPriceAvailableBitmapFilter( innerRecordPrice.priceId(), entityPrimaryKey, innerRecordPrice.innerRecordId(), - NumberUtils.convertToInt(innerRecordPrice.priceWithTax(), indexedPricePlaces), - NumberUtils.convertToInt(innerRecordPrice.priceWithoutTax(), indexedPricePlaces) + NumberUtils.convertExternalNumberToInt(innerRecordPrice.priceWithTax(), indexedPricePlaces), + NumberUtils.convertExternalNumberToInt(innerRecordPrice.priceWithoutTax(), indexedPricePlaces) ) ); } @@ -136,8 +136,8 @@ public SellingPriceAvailableBitmapFilter( return new CumulatedVirtualPriceRecord( entityPrimaryKey, priceQueryMode == QueryPriceMode.WITH_TAX ? - NumberUtils.convertToInt(cumulatedPrice.priceWithTax(), indexedPricePlaces) : - NumberUtils.convertToInt(cumulatedPrice.priceWithoutTax(), indexedPricePlaces), + NumberUtils.convertExternalNumberToInt(cumulatedPrice.priceWithTax(), indexedPricePlaces) : + NumberUtils.convertExternalNumberToInt(cumulatedPrice.priceWithoutTax(), indexedPricePlaces), priceQueryMode, intSetInnerRecordIds ); @@ -147,8 +147,8 @@ public SellingPriceAvailableBitmapFilter( priceWithInternalIds.getInternalPriceId() : -1, priceContract.priceId(), entityPrimaryKey, - NumberUtils.convertToInt(priceContract.priceWithTax(), indexedPricePlaces), - NumberUtils.convertToInt(priceContract.priceWithoutTax(), indexedPricePlaces) + NumberUtils.convertExternalNumberToInt(priceContract.priceWithTax(), indexedPricePlaces), + NumberUtils.convertExternalNumberToInt(priceContract.priceWithoutTax(), indexedPricePlaces) ); } else { return new PriceRecordInnerRecordSpecific( @@ -157,8 +157,8 @@ public SellingPriceAvailableBitmapFilter( priceContract.priceId(), entityPrimaryKey, priceContract.innerRecordId(), - NumberUtils.convertToInt(priceContract.priceWithTax(), indexedPricePlaces), - NumberUtils.convertToInt(priceContract.priceWithoutTax(), indexedPricePlaces) + NumberUtils.convertExternalNumberToInt(priceContract.priceWithTax(), indexedPricePlaces), + NumberUtils.convertExternalNumberToInt(priceContract.priceWithoutTax(), indexedPricePlaces) ); } }; diff --git a/evita_engine/src/main/java/io/evitadb/index/mutation/index/PriceIndexMutator.java b/evita_engine/src/main/java/io/evitadb/index/mutation/index/PriceIndexMutator.java index 3d651cf3d3..076f6f3a79 100644 --- a/evita_engine/src/main/java/io/evitadb/index/mutation/index/PriceIndexMutator.java +++ b/evita_engine/src/main/java/io/evitadb/index/mutation/index/PriceIndexMutator.java @@ -33,6 +33,7 @@ import io.evitadb.store.entity.model.entity.price.PriceInternalIdContainer; import io.evitadb.store.entity.model.entity.price.PriceWithInternalIds; import io.evitadb.utils.Assert; +import io.evitadb.utils.NumberUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -41,8 +42,6 @@ import java.util.function.BiFunction; import java.util.function.Consumer; -import static io.evitadb.utils.NumberUtils.convertToInt; - /** * This interface is used to co-locate price mutating routines which are rather procedural and long to avoid excessive * amount of code in {@link EntityIndexLocalMutationExecutor}. @@ -108,8 +107,8 @@ static void priceUpsert( final Integer formerInternalPriceId = Objects.requireNonNull(formerPrice.getInternalPriceId()); final Integer formerInnerRecordId = formerPrice.innerRecordId(); final DateTimeRange formerValidity = formerPrice.validity(); - final int formerPriceWithoutTax = convertToInt(formerPrice.priceWithoutTax(), indexedPricePlaces); - final int formerPriceWithTax = convertToInt(formerPrice.priceWithTax(), indexedPricePlaces); + final int formerPriceWithoutTax = NumberUtils.convertExternalNumberToInt(formerPrice.priceWithoutTax(), indexedPricePlaces); + final int formerPriceWithTax = NumberUtils.convertExternalNumberToInt(formerPrice.priceWithTax(), indexedPricePlaces); entityIndex.priceRemove( entityPrimaryKey, formerInternalPriceId, @@ -135,8 +134,8 @@ static void priceUpsert( if (indexed) { final PriceInternalIdContainer internalPriceIds = internalIdSupplier.apply(priceKey, innerRecordId); final Integer internalPriceId = internalPriceIds.getInternalPriceId(); - final int priceWithoutTaxAsInt = convertToInt(priceWithoutTax, indexedPricePlaces); - final int priceWithTaxAsInt = convertToInt(priceWithTax, indexedPricePlaces); + final int priceWithoutTaxAsInt = NumberUtils.convertExternalNumberToInt(priceWithoutTax, indexedPricePlaces); + final int priceWithTaxAsInt = NumberUtils.convertExternalNumberToInt(priceWithTax, indexedPricePlaces); final PriceInternalIdContainer priceId = entityIndex.addPrice( entityPrimaryKey, internalPriceId, @@ -198,8 +197,8 @@ static void priceRemove( final int internalPriceId = formerPrice.getInternalPriceId(); final Integer innerRecordId = formerPrice.innerRecordId(); final DateTimeRange validity = formerPrice.validity(); - final int priceWithoutTax = convertToInt(formerPrice.priceWithoutTax(), indexedPricePlaces); - final int priceWithTax = convertToInt(formerPrice.priceWithTax(), indexedPricePlaces); + final int priceWithoutTax = NumberUtils.convertExternalNumberToInt(formerPrice.priceWithoutTax(), indexedPricePlaces); + final int priceWithTax = NumberUtils.convertExternalNumberToInt(formerPrice.priceWithTax(), indexedPricePlaces); entityIndex.priceRemove( entityPrimaryKey, internalPriceId, @@ -244,4 +243,5 @@ static BiFunction createPriceProvid return price; }; } + } From 954162ae542979258cb795a591ecbe2dc8c72559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Thu, 31 Oct 2024 14:24:27 +0100 Subject: [PATCH 2/3] fix: indexed numbers should be rounded to specified number of decimal places (cherry picked from commit d5158de0ddef44cd29615fda390fda9a2f4c4243) --- .../src/main/java/io/evitadb/utils/NumberUtils.java | 6 +++++- .../src/test/java/io/evitadb/utils/NumberUtilsTest.java | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/evita_common/src/main/java/io/evitadb/utils/NumberUtils.java b/evita_common/src/main/java/io/evitadb/utils/NumberUtils.java index fa310e2229..9fd92fde8b 100644 --- a/evita_common/src/main/java/io/evitadb/utils/NumberUtils.java +++ b/evita_common/src/main/java/io/evitadb/utils/NumberUtils.java @@ -27,6 +27,7 @@ import javax.annotation.Nonnull; import java.math.BigDecimal; +import java.math.RoundingMode; /** * String utils contains shared utility method for working with Numbers. @@ -153,7 +154,10 @@ public static int convertToInt(@Nonnull Number number) { */ public static int convertToInt(@Nonnull BigDecimal number, int acceptDecimalPlaces) { try { - return number.stripTrailingZeros().scaleByPowerOfTen(acceptDecimalPlaces).intValueExact(); + return number.stripTrailingZeros() + .scaleByPowerOfTen(acceptDecimalPlaces) + .setScale(0, RoundingMode.HALF_UP) + .intValueExact(); } catch (ArithmeticException ex) { throw new ArithmeticException( "Cannot convert big decimal " + number + diff --git a/evita_functional_tests/src/test/java/io/evitadb/utils/NumberUtilsTest.java b/evita_functional_tests/src/test/java/io/evitadb/utils/NumberUtilsTest.java index fa22f2b693..c6cddb4e9e 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/utils/NumberUtilsTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/utils/NumberUtilsTest.java @@ -146,6 +146,9 @@ void shouldConvertNumbersToInt() { assertEquals(2, NumberUtils.convertToInt((short) 2)); assertEquals(2, NumberUtils.convertToInt(2)); assertEquals(2, NumberUtils.convertToInt((long) 2)); + assertEquals(22314, NumberUtils.convertToInt(new BigDecimal("223.1405"), 2)); + assertEquals(2231405, NumberUtils.convertToInt(new BigDecimal("223.1405"), 4)); + assertEquals(223, NumberUtils.convertToInt(new BigDecimal("223.1405"), 0)); } @Test From 2ec9917c1252f6b0e74d37196ed59a1c6f3c1ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Thu, 31 Oct 2024 14:36:20 +0100 Subject: [PATCH 3/3] fix: indexed numbers should be rounded to specified number of decimal places (cherry picked from commit e253ad9453e843029fdac8b37ab74bd397b655e9) --- .../src/test/java/io/evitadb/utils/NumberUtilsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evita_functional_tests/src/test/java/io/evitadb/utils/NumberUtilsTest.java b/evita_functional_tests/src/test/java/io/evitadb/utils/NumberUtilsTest.java index c6cddb4e9e..b483fe9122 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/utils/NumberUtilsTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/utils/NumberUtilsTest.java @@ -163,7 +163,7 @@ void shouldConvertBigDecimalToInt() { assertEquals(11020, NumberUtils.convertToInt(new BigDecimal("110.2"), 2)); assertEquals(11020, NumberUtils.convertToInt(new BigDecimal("110.20"), 2)); assertEquals(11020, NumberUtils.convertToInt(new BigDecimal("110.2000"), 2)); - assertThrows(IllegalArgumentException.class, () -> NumberUtils.convertToInt(new BigDecimal("110.202"), 2)); + assertThrows(ArithmeticException.class, () -> NumberUtils.convertToInt(new BigDecimal("21474836471"), 2)); } @Test