This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/dev/pr-12182-de345265d0bc6e9fcbd5e2a7096ae33faff4fb6e in repository https://gitbox.apache.org/repos/asf/seatunnel.git
commit c2f88ba3776d4751a427e448aa68f2541162eaa8 Author: Sepuri Sai Krishna <[email protected]> AuthorDate: Tue Sep 22 15:38:56 2026 +0000 [Test][Transform-V2] Cover the MOD divisor type contract and its overflow bounds (#12182) --- .../sql/zeta/functions/NumericFunction.java | 7 ++++++ .../transform/sql/zeta/NumericFunctionTest.java | 26 ++++++++++++++++++++ .../sql/zeta/functions/NumericFunctionTest.java | 28 ++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/NumericFunction.java b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/NumericFunction.java index 19cb1f91d4..746adb7def 100644 --- a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/NumericFunction.java +++ b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/NumericFunction.java @@ -221,6 +221,13 @@ public class NumericFunction { CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION, "Mod by zero"); } BigDecimal[] res = leftBD.divideAndRemainder(rightBD); + // The result keeps the type of the divisor, which is what ZetaSQLType declares for MOD. + // No range check is needed for the narrow types: divideAndRemainder guarantees that + // |remainder| < |divisor|, so the remainder is always within the divisor's range. + // In range, not lossless: a FLOAT, DOUBLE or DECIMAL dividend keeps its fractional + // part, and byteValue() and shortValue() truncate it, so MOD(5.5, (byte) 2) is 1 and + // not 1.5. That is deliberate and matches the type ZetaSQLType declares, so it should + // not be "fixed" into rounding or into a wider return type. if (rightValue instanceof Byte) { return res[1].byteValue(); } diff --git a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/NumericFunctionTest.java b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/NumericFunctionTest.java index 66e26f4d69..1a290e0257 100644 --- a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/NumericFunctionTest.java +++ b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/NumericFunctionTest.java @@ -185,4 +185,30 @@ public class NumericFunctionTest { new BigDecimal("12345678901234567890.987654321"), new BigDecimal("1")))); } + + /** + * ZetaSQLType derives the MOD result type from the divisor, so the row type the planner + * declares has to agree with what the runtime actually produces. Only the declared type is + * asserted here: the projected values for the same query shape are already covered by {@code + * SQLNumericFunctionsTest#testModWithTinyIntAndSmallIntDivisors}, which landed with #12215. + */ + @Test + public void testModDeclaresTheDivisorTypeForTinyIntAndSmallInt() { + SeaTunnelRowType rowType = + new SeaTunnelRowType( + new String[] {"int_v", "tiny_v", "small_v"}, + new SeaTunnelDataType[] { + BasicType.INT_TYPE, BasicType.BYTE_TYPE, BasicType.SHORT_TYPE + }); + SQLEngine sqlEngine = SQLEngineFactory.getSQLEngine(SQLEngineFactory.EngineType.ZETA); + sqlEngine.init( + "test", + null, + rowType, + "select MOD(int_v, tiny_v) as tiny_mod, MOD(int_v, small_v) as small_mod from test"); + + SeaTunnelRowType outRowType = sqlEngine.typeMapping(null); + Assertions.assertEquals(BasicType.BYTE_TYPE, outRowType.getFieldType(0)); + Assertions.assertEquals(BasicType.SHORT_TYPE, outRowType.getFieldType(1)); + } } diff --git a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/NumericFunctionTest.java b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/NumericFunctionTest.java index 3f4c835e4e..a3324346a5 100644 --- a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/NumericFunctionTest.java +++ b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/NumericFunctionTest.java @@ -145,6 +145,34 @@ public class NumericFunctionTest { Assertions.assertEquals(1, NumericFunction.mod(Arrays.asList(5, 2))); Assertions.assertEquals(1L, NumericFunction.mod(Arrays.asList(5L, 2L))); + // A TINYINT or SMALLINT divisor has to carry its own type into the result, which is what + // ZetaSQLType declares for MOD (added with #12215). + Object byteResult = NumericFunction.mod(Arrays.asList(5, (byte) 2)); + Assertions.assertEquals(Byte.class, byteResult.getClass()); + Assertions.assertEquals((byte) 1, byteResult); + + Object shortResult = NumericFunction.mod(Arrays.asList(5, (short) 2)); + Assertions.assertEquals(Short.class, shortResult.getClass()); + Assertions.assertEquals((short) 1, shortResult); + + // What bounds the result is the remainder, not the divisor: divideAndRemainder gives + // |remainder| <= |divisor| - 1, so the extreme outputs are the ones below. A dividend + // merely larger than the divisor does not reach them, so these are the inputs that + // would fail first if a range guard were ever added or the narrowing became lossy. + // The sign follows the dividend, which the negative cases pin. + Assertions.assertEquals( + (byte) 127, NumericFunction.mod(Arrays.asList(127, Byte.MIN_VALUE))); + Assertions.assertEquals( + (byte) -127, NumericFunction.mod(Arrays.asList(-127, Byte.MIN_VALUE))); + Assertions.assertEquals( + (short) 32767, NumericFunction.mod(Arrays.asList(32767, Short.MIN_VALUE))); + Assertions.assertEquals( + (short) -32767, NumericFunction.mod(Arrays.asList(-32767, Short.MIN_VALUE))); + + // A fractional remainder is truncated to the divisor's integral type rather than + // rounded, which is what ZetaSQLType declares the result to be. + Assertions.assertEquals((byte) 1, NumericFunction.mod(Arrays.asList(5.5d, (byte) 2))); + Float floatResult = (Float) NumericFunction.mod(Arrays.asList(5.5f, 2.0f)); Assertions.assertEquals(1.5f, floatResult);
