This is an automated email from the ASF dual-hosted git repository. aadamchik pushed a commit to branch CAY-2660 in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 3e00d0e7ff9c16726e9d861439cdf3c136a7870c Author: Andrus Adamchik <[email protected]> AuthorDate: Fri Jun 5 13:55:12 2020 +0300 CAY-2660 BigDecimals that differ only in scale are treated as different values causing unneeded updates --- .../main/java/org/apache/cayenne/util/Util.java | 12 ++- .../DataContextEJBQLNumericalFunctionalIT.java | 12 +-- .../org/apache/cayenne/access/NumericTypesIT.java | 99 +++++++++++++++++----- .../numeric_types/auto/_BigDecimalEntity.java | 47 +++++++--- .../org/apache/cayenne/unit/di/CommitStats.java | 50 +++++++++++ .../src/test/resources/cayenne-numeric-types.xml | 2 + .../src/test/resources/numeric-types.map.xml | 8 +- 7 files changed, 186 insertions(+), 44 deletions(-) diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/Util.java b/cayenne-server/src/main/java/org/apache/cayenne/util/Util.java index c86c279..ed334b7 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/Util.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/Util.java @@ -53,6 +53,7 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Member; import java.lang.reflect.Modifier; +import java.math.BigDecimal; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -208,8 +209,15 @@ public class Util { builder.append(o1, o2); return builder.isEquals(); } else { // It is NOT an array, so use regular equals() - return o1.equals(o2); - } + boolean eq = o1.equals(o2); + + // special case - BigDecimals that differ only in trailing zeros + if (!eq && o1 instanceof BigDecimal && o2 instanceof BigDecimal) { + return ((BigDecimal) o1).compareTo((BigDecimal) o2) == 0; + } + + return eq; + } } /** diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextEJBQLNumericalFunctionalIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextEJBQLNumericalFunctionalIT.java index 10db03c..76287b8 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextEJBQLNumericalFunctionalIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextEJBQLNumericalFunctionalIT.java @@ -64,15 +64,15 @@ public class DataContextEJBQLNumericalFunctionalIT extends ServerCase { public void testABS() { BigDecimalEntity o1 = context.newObject(BigDecimalEntity.class); - o1.setBigDecimalField(new BigDecimal("4.1")); + o1.setBigDecimalNumeric(new BigDecimal("4.1")); BigDecimalEntity o2 = context.newObject(BigDecimalEntity.class); - o2.setBigDecimalField(new BigDecimal("-5.1")); + o2.setBigDecimalNumeric(new BigDecimal("-5.1")); context.commitChanges(); EJBQLQuery query = new EJBQLQuery( - "SELECT d FROM BigDecimalEntity d WHERE ABS(d.bigDecimalField) < 5.0"); + "SELECT d FROM BigDecimalEntity d WHERE ABS(d.bigDecimalNumeric) < 5.0"); List<?> objects = context.performQuery(query); assertEquals(1, objects.size()); assertTrue(objects.contains(o1)); @@ -82,15 +82,15 @@ public class DataContextEJBQLNumericalFunctionalIT extends ServerCase { public void testSQRT() { BigDecimalEntity o1 = context.newObject(BigDecimalEntity.class); - o1.setBigDecimalField(new BigDecimal("9")); + o1.setBigDecimalNumeric(new BigDecimal("9")); BigDecimalEntity o2 = context.newObject(BigDecimalEntity.class); - o2.setBigDecimalField(new BigDecimal("16")); + o2.setBigDecimalNumeric(new BigDecimal("16")); context.commitChanges(); EJBQLQuery query = new EJBQLQuery( - "SELECT d FROM BigDecimalEntity d WHERE SQRT(d.bigDecimalField) > 3.1"); + "SELECT d FROM BigDecimalEntity d WHERE SQRT(d.bigDecimalNumeric) > 3.1"); List<?> objects = context.performQuery(query); assertEquals(1, objects.size()); assertTrue(objects.contains(o2)); diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/NumericTypesIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/NumericTypesIT.java index 7189dfb..3d4398d 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/access/NumericTypesIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/NumericTypesIT.java @@ -41,18 +41,21 @@ import org.apache.cayenne.testdo.numeric_types.DecimalPKTestEntity; import org.apache.cayenne.testdo.numeric_types.LongEntity; import org.apache.cayenne.testdo.numeric_types.SmallintTestEntity; import org.apache.cayenne.testdo.numeric_types.TinyintTestEntity; +import org.apache.cayenne.unit.di.CommitStats; +import org.apache.cayenne.unit.di.DataChannelInterceptor; +import org.apache.cayenne.unit.di.DataChannelSyncStats; import org.apache.cayenne.unit.di.server.CayenneProjects; import org.apache.cayenne.unit.di.server.ServerCase; import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; +import static org.junit.Assert.*; /** + * */ @UseServerRuntime(CayenneProjects.NUMERIC_TYPES_PROJECT) public class NumericTypesIT extends ServerCase { @@ -69,11 +72,15 @@ public class NumericTypesIT extends ServerCase { @Inject protected DBHelper dbHelper; + private final CommitStats commitStats = new CommitStats(() -> runtime.getDataDomain()); + protected TableHelper tSmallintTest; protected TableHelper tTinyintTest; @Before - public void setUp() throws Exception { + public void before() { + commitStats.before(); + tSmallintTest = new TableHelper(dbHelper, "SMALLINT_TEST"); tSmallintTest.setColumns("ID", "SMALLINT_COL"); @@ -81,6 +88,11 @@ public class NumericTypesIT extends ServerCase { tTinyintTest.setColumns("ID", "TINYINT_COL"); } + @After + public void after() { + commitStats.after(); + } + protected void createShortDataSet() throws Exception { tSmallintTest.insert(1, 9999); tSmallintTest.insert(2, 3333); @@ -128,21 +140,70 @@ public class NumericTypesIT extends ServerCase { } @Test - public void testBigDecimal() throws Exception { - - BigDecimalEntity test = context.newObject(BigDecimalEntity.class); - - BigDecimal i = new BigDecimal("1234567890.44"); - test.setBigDecimalField(i); - context.commitChanges(); - - BigDecimalEntity testRead = ObjectSelect.query(BigDecimalEntity.class) - .selectFirst(context); - assertNotNull(testRead.getBigDecimalField()); - assertEquals(i, testRead.getBigDecimalField()); + public void testBigDecimal_Decimal() { + + // this matches the column scale exactly + String v1 = "7890.123456"; + // this has lower scale than the column + String v2 = "7890.1"; + String v2_padded = "7890.100000"; + + BigDecimalEntity o = context.newObject(BigDecimalEntity.class); + o.setBigDecimalDecimal(new BigDecimal(v1)); + o.getObjectContext().commitChanges(); + assertEquals(1, commitStats.getCommitCount()); + BigDecimalEntity o1 = ObjectSelect.query(BigDecimalEntity.class).selectFirst(runtime.newContext()); + assertEquals(v1, o1.getBigDecimalDecimal().toString()); + + o.setBigDecimalDecimal(new BigDecimal(v2)); + o.getObjectContext().commitChanges(); + BigDecimalEntity o2 = ObjectSelect.query(BigDecimalEntity.class).selectFirst(runtime.newContext()); + assertEquals(v2_padded, o2.getBigDecimalDecimal().toString()); + assertEquals(2, commitStats.getCommitCount()); + + o2.setBigDecimalDecimal(new BigDecimal(v2)); + o2.getObjectContext().commitChanges(); + assertEquals("Commit was not expected. The difference is purely in value padding", 2, commitStats.getCommitCount()); + BigDecimalEntity o3 = ObjectSelect.query(BigDecimalEntity.class).selectFirst(runtime.newContext()); + assertEquals(v2_padded, o3.getBigDecimalDecimal().toString()); + + o3.setBigDecimalDecimal(null); + o3.getObjectContext().commitChanges(); + assertEquals(3, commitStats.getCommitCount()); + BigDecimalEntity o4 = ObjectSelect.query(BigDecimalEntity.class).selectFirst(runtime.newContext()); + assertNull(o4.getBigDecimalDecimal()); + } - test.setBigDecimalField(null); - context.commitChanges(); + @Test + public void testBigDecimal_Numeric() { + + String v1 = "1234567890.44"; + String v2 = "1234567890.4"; + String v2_padded = "1234567890.40"; + + BigDecimalEntity o = context.newObject(BigDecimalEntity.class); + o.setBigDecimalNumeric(new BigDecimal(v1)); + o.getObjectContext().commitChanges(); + assertEquals(1, commitStats.getCommitCount()); + BigDecimalEntity o1 = ObjectSelect.query(BigDecimalEntity.class).selectFirst(runtime.newContext()); + assertEquals(v1, o1.getBigDecimalNumeric().toString()); + + o1.setBigDecimalNumeric(new BigDecimal(v2)); + o1.getObjectContext().commitChanges(); + assertEquals(2, commitStats.getCommitCount()); + BigDecimalEntity o2 = ObjectSelect.query(BigDecimalEntity.class).selectFirst(runtime.newContext()); + assertEquals(v2_padded, o2.getBigDecimalNumeric().toString()); + + o2.setBigDecimalNumeric(new BigDecimal(v2)); + assertEquals("Commit was not expected. The difference is purely in value padding", 2, commitStats.getCommitCount()); + BigDecimalEntity o3 = ObjectSelect.query(BigDecimalEntity.class).selectFirst(runtime.newContext()); + assertEquals(v2_padded, o3.getBigDecimalNumeric().toString()); + + o3.setBigDecimalNumeric(null); + o3.getObjectContext().commitChanges(); + assertEquals(3, commitStats.getCommitCount()); + BigDecimalEntity o4 = ObjectSelect.query(BigDecimalEntity.class).selectFirst(runtime.newContext()); + assertNull(o4.getBigDecimalNumeric()); } @Test diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/numeric_types/auto/_BigDecimalEntity.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/numeric_types/auto/_BigDecimalEntity.java index b0473f0..20b6cce 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/numeric_types/auto/_BigDecimalEntity.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/numeric_types/auto/_BigDecimalEntity.java @@ -21,19 +21,31 @@ public abstract class _BigDecimalEntity extends BaseDataObject { public static final String ID_PK_COLUMN = "ID"; - public static final NumericProperty<BigDecimal> BIG_DECIMAL_FIELD = PropertyFactory.createNumeric("bigDecimalField", BigDecimal.class); + public static final NumericProperty<BigDecimal> BIG_DECIMAL_DECIMAL = PropertyFactory.createNumeric("bigDecimalDecimal", BigDecimal.class); + public static final NumericProperty<BigDecimal> BIG_DECIMAL_NUMERIC = PropertyFactory.createNumeric("bigDecimalNumeric", BigDecimal.class); - protected BigDecimal bigDecimalField; + protected BigDecimal bigDecimalDecimal; + protected BigDecimal bigDecimalNumeric; - public void setBigDecimalField(BigDecimal bigDecimalField) { - beforePropertyWrite("bigDecimalField", this.bigDecimalField, bigDecimalField); - this.bigDecimalField = bigDecimalField; + public void setBigDecimalDecimal(BigDecimal bigDecimalDecimal) { + beforePropertyWrite("bigDecimalDecimal", this.bigDecimalDecimal, bigDecimalDecimal); + this.bigDecimalDecimal = bigDecimalDecimal; } - public BigDecimal getBigDecimalField() { - beforePropertyRead("bigDecimalField"); - return this.bigDecimalField; + public BigDecimal getBigDecimalDecimal() { + beforePropertyRead("bigDecimalDecimal"); + return this.bigDecimalDecimal; + } + + public void setBigDecimalNumeric(BigDecimal bigDecimalNumeric) { + beforePropertyWrite("bigDecimalNumeric", this.bigDecimalNumeric, bigDecimalNumeric); + this.bigDecimalNumeric = bigDecimalNumeric; + } + + public BigDecimal getBigDecimalNumeric() { + beforePropertyRead("bigDecimalNumeric"); + return this.bigDecimalNumeric; } @Override @@ -43,8 +55,10 @@ public abstract class _BigDecimalEntity extends BaseDataObject { } switch(propName) { - case "bigDecimalField": - return this.bigDecimalField; + case "bigDecimalDecimal": + return this.bigDecimalDecimal; + case "bigDecimalNumeric": + return this.bigDecimalNumeric; default: return super.readPropertyDirectly(propName); } @@ -57,8 +71,11 @@ public abstract class _BigDecimalEntity extends BaseDataObject { } switch (propName) { - case "bigDecimalField": - this.bigDecimalField = (BigDecimal)val; + case "bigDecimalDecimal": + this.bigDecimalDecimal = (BigDecimal)val; + break; + case "bigDecimalNumeric": + this.bigDecimalNumeric = (BigDecimal)val; break; default: super.writePropertyDirectly(propName, val); @@ -76,13 +93,15 @@ public abstract class _BigDecimalEntity extends BaseDataObject { @Override protected void writeState(ObjectOutputStream out) throws IOException { super.writeState(out); - out.writeObject(this.bigDecimalField); + out.writeObject(this.bigDecimalDecimal); + out.writeObject(this.bigDecimalNumeric); } @Override protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { super.readState(in); - this.bigDecimalField = (BigDecimal)in.readObject(); + this.bigDecimalDecimal = (BigDecimal)in.readObject(); + this.bigDecimalNumeric = (BigDecimal)in.readObject(); } } diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/CommitStats.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/CommitStats.java new file mode 100644 index 0000000..4679463 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/CommitStats.java @@ -0,0 +1,50 @@ +package org.apache.cayenne.unit.di; + +import org.apache.cayenne.DataChannel; +import org.apache.cayenne.DataChannelSyncFilter; +import org.apache.cayenne.DataChannelSyncFilterChain; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.access.DataDomain; +import org.apache.cayenne.graph.GraphDiff; +import org.junit.rules.ExternalResource; + +import java.util.function.Supplier; + +public class CommitStats implements DataChannelSyncFilter { + + private int commitCount; + private Supplier<DataDomain> dataDomain; + + public CommitStats(Supplier<DataDomain> dataDomain) { + this.dataDomain = dataDomain; + } + + public void before() { + dataDomain.get().addSyncFilter(this); + commitCount = 0; + } + + public void after() { + dataDomain.get().removeSyncFilter(this); + } + + @Override + public GraphDiff onSync( + ObjectContext originatingContext, + GraphDiff changes, + int syncType, + DataChannelSyncFilterChain filterChain) { + + switch (syncType) { + case DataChannel.FLUSH_CASCADE_SYNC: + commitCount++; + break; + } + + return filterChain.onSync(originatingContext, changes, syncType); + } + + public int getCommitCount() { + return commitCount; + } +} diff --git a/cayenne-server/src/test/resources/cayenne-numeric-types.xml b/cayenne-server/src/test/resources/cayenne-numeric-types.xml index cb44482..d9c33bf 100644 --- a/cayenne-server/src/test/resources/cayenne-numeric-types.xml +++ b/cayenne-server/src/test/resources/cayenne-numeric-types.xml @@ -1,5 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <domain xmlns="http://cayenne.apache.org/schema/10/domain" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://cayenne.apache.org/schema/10/domain https://cayenne.apache.org/schema/10/domain.xsd" project-version="10"> <map name="numeric-types"/> </domain> diff --git a/cayenne-server/src/test/resources/numeric-types.map.xml b/cayenne-server/src/test/resources/numeric-types.map.xml index 65d4000..d53a680 100644 --- a/cayenne-server/src/test/resources/numeric-types.map.xml +++ b/cayenne-server/src/test/resources/numeric-types.map.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <data-map xmlns="http://cayenne.apache.org/schema/10/modelMap" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap http://cayenne.apache.org/schema/10/modelMap.xsd" + xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap https://cayenne.apache.org/schema/10/modelMap.xsd" project-version="10"> <property name="defaultPackage" value="org.apache.cayenne.testdo.numeric_types"/> <property name="defaultSuperclass" value="org.apache.cayenne.CayenneDataObject"/> @@ -9,7 +9,8 @@ <property name="defaultClientPackage" value="test.client"/> <property name="defaultClientSuperclass" value="org.apache.cayenne.PersistentObject"/> <db-entity name="BIGDECIMAL_ENTITY"> - <db-attribute name="BIGDECIMAL_FIELD" type="NUMERIC" length="12" scale="2"/> + <db-attribute name="BIG_DECIMAL_DECIMAL" type="DECIMAL" length="12" scale="6"/> + <db-attribute name="BIG_DECIMAL_NUMERIC" type="NUMERIC" length="12" scale="2"/> <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/> </db-entity> <db-entity name="BIGINTEGER_ENTITY"> @@ -45,7 +46,8 @@ <db-attribute name="TINYINT_COL" type="TINYINT"/> </db-entity> <obj-entity name="BigDecimalEntity" className="org.apache.cayenne.testdo.numeric_types.BigDecimalEntity" dbEntityName="BIGDECIMAL_ENTITY"> - <obj-attribute name="bigDecimalField" type="java.math.BigDecimal" db-attribute-path="BIGDECIMAL_FIELD"/> + <obj-attribute name="bigDecimalDecimal" type="java.math.BigDecimal" db-attribute-path="BIG_DECIMAL_DECIMAL"/> + <obj-attribute name="bigDecimalNumeric" type="java.math.BigDecimal" db-attribute-path="BIG_DECIMAL_NUMERIC"/> </obj-entity> <obj-entity name="BigIntegerEntity" className="org.apache.cayenne.testdo.numeric_types.BigIntegerEntity" dbEntityName="BIGINTEGER_ENTITY"> <obj-attribute name="bigIntegerField" type="java.math.BigInteger" db-attribute-path="BIG_INTEGER_FIELD"/>
