Skip to content

Commit eace072

Browse files
committed
PLUGIN-1925: Introduce Custom BigDecimal Splitter
1 parent 90856c0 commit eace072

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright © 2025 Cask Data, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package io.cdap.plugin.db.source;
18+
19+
import org.apache.hadoop.mapreduce.lib.db.BigDecimalSplitter;
20+
import java.math.BigDecimal;
21+
import java.math.RoundingMode;
22+
23+
/**
24+
* Custom implementation of {@link BigDecimalSplitter} to ensures safe and precise division of BigDecimal values while
25+
* calculating split points for NUMERIC and DECIMAL types.
26+
*/
27+
public class CustomBigDecimalSplitter extends BigDecimalSplitter {
28+
29+
public static final int SCALE_BUFFER = 5;
30+
/**
31+
* Performs safe division with correct scale handling.
32+
*
33+
* @param numerator the dividend (BigDecimal)
34+
* @param denominator the divisor (BigDecimal)
35+
* @return quotient with derived scale
36+
* @throws ArithmeticException if denominator is zero
37+
*/
38+
@Override
39+
protected BigDecimal tryDivide(BigDecimal numerator, BigDecimal denominator) {
40+
if (denominator.compareTo(numerator) == 0) {
41+
throw new ArithmeticException("Numerator and Denominator are equal in splitter");
42+
}
43+
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
44+
throw new ArithmeticException("Division by zero in splitter");
45+
}
46+
// Derive scale from numerator/denominator + buffer
47+
int effectiveScale = Math.max(numerator.scale(), denominator.scale()) + SCALE_BUFFER;
48+
return numerator.divide(denominator, effectiveScale, RoundingMode.HALF_UP);
49+
}
50+
}

database-commons/src/main/java/io/cdap/plugin/db/source/DataDrivenETLDBInputFormat.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.hadoop.mapreduce.TaskAttemptContext;
2929
import org.apache.hadoop.mapreduce.lib.db.DBConfiguration;
3030
import org.apache.hadoop.mapreduce.lib.db.DBInputFormat;
31+
import org.apache.hadoop.mapreduce.lib.db.DBSplitter;
3132
import org.apache.hadoop.mapreduce.lib.db.DBWritable;
3233
import org.apache.hadoop.mapreduce.lib.db.DataDrivenDBInputFormat;
3334
import org.slf4j.Logger;
@@ -39,6 +40,7 @@
3940
import java.sql.DriverManager;
4041
import java.sql.SQLException;
4142
import java.sql.Statement;
43+
import java.sql.Types;
4244
import java.util.Properties;
4345

4446
/**
@@ -128,6 +130,14 @@ public Connection createConnection() {
128130
return getConnection();
129131
}
130132

133+
@Override
134+
protected DBSplitter getSplitter(int sqlDataType) {
135+
if (sqlDataType == Types.NUMERIC || sqlDataType == Types.DECIMAL) {
136+
return new CustomBigDecimalSplitter();
137+
}
138+
return super.getSplitter(sqlDataType);
139+
}
140+
131141
@Override
132142
public RecordReader createDBRecordReader(DBInputSplit split, Configuration conf) throws IOException {
133143
final RecordReader dbRecordReader = super.createDBRecordReader(split, conf);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright © 2025 Cask Data, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package io.cdap.plugin.db.source;
18+
19+
import org.junit.Test;
20+
import java.math.BigDecimal;
21+
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertThrows;
23+
import static org.junit.Assert.assertTrue;
24+
25+
/**
26+
* Test class for {@link CustomBigDecimalSplitter}
27+
*/
28+
public class CustomBigDecimalSplitterTest {
29+
private final CustomBigDecimalSplitter splitter = new CustomBigDecimalSplitter();
30+
31+
@Test
32+
public void testSmallRangeDivision() {
33+
BigDecimal result = splitter.tryDivide(BigDecimal.ONE, new BigDecimal("4"));
34+
assertEquals(new BigDecimal("0.25000"), result);
35+
}
36+
37+
@Test
38+
public void testLargePrecision() {
39+
BigDecimal numerator = new BigDecimal("1.0000000000000000001");
40+
BigDecimal denominator = new BigDecimal("3");
41+
BigDecimal result = splitter.tryDivide(numerator, denominator);
42+
assertTrue(result.compareTo(BigDecimal.ZERO) > 0);
43+
}
44+
45+
@Test
46+
public void testDivisionByZero() {
47+
assertThrows(ArithmeticException.class, () ->
48+
splitter.tryDivide(BigDecimal.ONE, BigDecimal.ZERO));
49+
}
50+
51+
@Test
52+
public void testZeroNumerator() {
53+
// when minVal == maxVal
54+
BigDecimal result = splitter.tryDivide(BigDecimal.ZERO, BigDecimal.ONE);
55+
assertEquals(0, result.compareTo(BigDecimal.ZERO));
56+
}
57+
}

0 commit comments

Comments
 (0)