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-12337-128994ca0c6b6262b14037f5f90a390d513a4720
in repository https://gitbox.apache.org/repos/asf/seatunnel.git

commit 50315b321bc68d910352d78f0fcbff80872451f5
Author: Goutam Adwant <[email protected]>
AuthorDate: Wed Oct 28 13:28:59 2026 +0000

    [Fix][Transform-V2] Widen vector operands before arithmetic (#12337)
    
    Signed-off-by: Goutam Adwant <[email protected]>
---
 .../introduction/concepts/incompatible-changes.md  | 14 ++++
 docs/en/transforms/calcite.md                      |  8 ++
 docs/en/transforms/sql-functions.md                |  9 ++
 .../introduction/concepts/incompatible-changes.md  | 11 +++
 docs/zh/transforms/calcite.md                      |  6 ++
 docs/zh/transforms/sql-functions.md                |  6 ++
 .../calcite_builtin_functions.conf                 | 24 +++++-
 .../test/resources/sql_transform/func_vector.conf  | 51 ++++++++++++
 .../calcite/udf/CosineDistanceFunction.java        |  8 +-
 .../calcite/udf/InnerProductFunction.java          |  4 +-
 .../transform/calcite/udf/L1DistanceFunction.java  |  2 +-
 .../transform/calcite/udf/L2DistanceFunction.java  |  2 +-
 .../transform/calcite/udf/VectorNormFunction.java  |  2 +-
 .../calcite/udf/VectorNormalizeFunction.java       |  2 +-
 .../sql/zeta/functions/VectorFunction.java         | 20 +++--
 .../transform/calcite/CalciteSQLEngineTest.java    | 36 ++++++++
 .../transform/calcite/udf/VectorUdfTest.java       | 69 +++++++++++++++
 .../transform/sql/SQLVectorFunctionTest.java       | 35 ++++++++
 .../sql/zeta/functions/VectorFunctionTest.java     | 97 ++++++++++++++++++++++
 19 files changed, 389 insertions(+), 17 deletions(-)

diff --git a/docs/en/introduction/concepts/incompatible-changes.md 
b/docs/en/introduction/concepts/incompatible-changes.md
index 0b50a23ed2..39bdf4731f 100644
--- a/docs/en/introduction/concepts/incompatible-changes.md
+++ b/docs/en/introduction/concepts/incompatible-changes.md
@@ -241,6 +241,20 @@ The Zeta SQL ARRAY function now declares TINYINT elements 
as `ARRAY<TINYINT>`, m
     (`existingConfigMap`) and restrict the member port (5801) with a 
`NetworkPolicy`. Restart the pods
     after upgrading so the new configuration is applied.
 
+### SQL and Calcite Vector Arithmetic
+
+- **Behavior correction**: `VECTOR_NORM`, `INNER_PRODUCT`, `COSINE_DISTANCE`,
+  `L1_DISTANCE`, `L2_DISTANCE` and `VECTOR_NORMALIZE` now widen float elements 
before
+  intermediate multiplication or subtraction. Finite inputs no longer produce
+  incorrect infinity, NaN or zero merely because an intermediate float 
operation
+  overflows or underflows. Results can also differ in their low-order digits.
+- **Migration**: Review thresholds and previously computed vector metrics or 
normalized
+  embeddings before replaying data. No options, types, schemas or state 
formats change.
+  Null inputs and actual zero vectors retain their handling. No non-finite 
input
+  validation or sanitization is added; corrected finite intermediates can also
+  affect results when mixed with non-finite elements. Normalized output 
elements
+  remain float, and random-projection arithmetic is unaffected.
+
 ### SQL ARRAY_MAX and ARRAY_MIN Precision
 
 - **Behavior correction: exact integral and DECIMAL array extrema**
diff --git a/docs/en/transforms/calcite.md b/docs/en/transforms/calcite.md
index 53ba3107da..06fc6414f3 100644
--- a/docs/en/transforms/calcite.md
+++ b/docs/en/transforms/calcite.md
@@ -388,6 +388,14 @@ transform {
 
 ### Vector Operations
 
+`VECTOR_NORM`, `INNER_PRODUCT`, `COSINE_DISTANCE`, `L1_DISTANCE`, 
`L2_DISTANCE` and
+`VECTOR_NORMALIZE` use double-precision intermediate products and differences 
for
+float vector elements, avoiding float overflow or underflow for finite inputs.
+Normalized output elements remain float, so output rounding and underflow 
remain
+possible. Null inputs and actual zero vectors retain their handling. No 
non-finite
+input validation or sanitization is added; corrected finite intermediates can 
also
+affect results when mixed with non-finite elements.
+
 Use built-in vector UDFs to compute distances, reduce dimensions, or normalize 
vectors in a data pipeline (e.g., between Milvus/Qdrant source and sink).
 
 ```hocon
diff --git a/docs/en/transforms/sql-functions.md 
b/docs/en/transforms/sql-functions.md
index 09e1d85b02..0a3d10d52c 100644
--- a/docs/en/transforms/sql-functions.md
+++ b/docs/en/transforms/sql-functions.md
@@ -1443,6 +1443,15 @@ SELECT * FROM dual
 
 ## Vector Functions
 
+`VECTOR_NORM`, `INNER_PRODUCT`, `COSINE_DISTANCE`, `L1_DISTANCE`, 
`L2_DISTANCE` and
+`VECTOR_NORMALIZE` perform intermediate products and differences in double 
precision
+for float vector elements. This avoids float overflow or underflow for finite 
large
+or small elements. `VECTOR_NORMALIZE` still returns float elements; rounding 
and
+underflow in the output representation remain possible. Null inputs and actual 
zero
+vectors retain their handling. No non-finite input validation or sanitization 
is
+added; corrected finite intermediates can also affect results when mixed with
+non-finite elements.
+
 Vector functions do not consume or modify their input vector buffers. The same 
vector field can be used in multiple expressions or as both arguments to a 
distance function, and remains available to downstream transforms and sinks.
 
 ### VECTOR_DIMS
diff --git a/docs/zh/introduction/concepts/incompatible-changes.md 
b/docs/zh/introduction/concepts/incompatible-changes.md
index b648d2c65b..24b14e119e 100644
--- a/docs/zh/introduction/concepts/incompatible-changes.md
+++ b/docs/zh/introduction/concepts/incompatible-changes.md
@@ -194,6 +194,17 @@ Zeta SQL ARRAY 函数现在将 TINYINT 元素声明为 `ARRAY<TINYINT>`,与实
   并使用 `schema_save_mode = "ERROR_WHEN_SCHEMA_NOT_EXIST"` 保留该表结构。不要对手动定义的目标表使用
   `RECREATE_SCHEMA`。任意指定索引前缀长度可能拒绝前缀相同但完整值不同的源主键,因此无法保持原有主键语义。
 
+### SQL 和 Calcite 向量运算
+
+- **行为修正**:`VECTOR_NORM`、`INNER_PRODUCT`、`COSINE_DISTANCE`、`L1_DISTANCE`、
+  `L2_DISTANCE` 和 `VECTOR_NORMALIZE` 在中间乘法或减法之前将 float 元素提升为 double。
+  有限输入不再仅因中间 float 运算溢出或下溢而得到错误的无穷大、NaN 或零,
+  结果的低位数字也可能发生变化。
+- **迁移建议**:重放数据前,请检查阈值以及此前计算的向量指标或归一化向量。
+  配置项、类型、模式和状态格式均不变。空输入和实际零向量的处理方式不变。
+  未增加非有限输入的校验或清理策略;修正有限中间结果也可能影响与非有限元素混合时的结果。
+  归一化输出元素仍为 float,随机投影运算不受影响。
+
 ### Helm Chart:默认关闭 Zeta REST API v1
 
 - **行为变更:Kubernetes Helm chart 不再默认开启未鉴权的 Zeta REST API v1**
diff --git a/docs/zh/transforms/calcite.md b/docs/zh/transforms/calcite.md
index 79bc1ecc33..b2bd2a1f52 100644
--- a/docs/zh/transforms/calcite.md
+++ b/docs/zh/transforms/calcite.md
@@ -388,6 +388,12 @@ transform {
 
 ### 向量运算
 
+`VECTOR_NORM`、`INNER_PRODUCT`、`COSINE_DISTANCE`、`L1_DISTANCE`、`L2_DISTANCE` 和
+`VECTOR_NORMALIZE` 对 float 向量元素使用 double 精度计算中间乘积和差值,
+避免有限输入的 float 中间运算溢出或下溢。归一化输出元素仍为 float,
+因此输出舍入或下溢仍可能发生。空输入和实际零向量的处理方式保持不变。
+未增加非有限输入的校验或清理策略;修正有限中间结果也可能影响与非有限元素混合时的结果。
+
 使用内置向量 UDF 在数据管线中计算距离、降维或归一化(例如 Milvus/Qdrant 源与目标之间的处理)。
 
 ```hocon
diff --git a/docs/zh/transforms/sql-functions.md 
b/docs/zh/transforms/sql-functions.md
index 460841af23..c21c958995 100644
--- a/docs/zh/transforms/sql-functions.md
+++ b/docs/zh/transforms/sql-functions.md
@@ -1437,6 +1437,12 @@ SELECT * FROM dual
 
 ## 向量函数
 
+`VECTOR_NORM`、`INNER_PRODUCT`、`COSINE_DISTANCE`、`L1_DISTANCE`、`L2_DISTANCE` 和
+`VECTOR_NORMALIZE` 对 float 向量元素使用 double 精度计算中间乘积和差值,
+避免有限的大数或小数在 float 中间运算中溢出或下溢。`VECTOR_NORMALIZE` 仍返回 float
+元素,输出表示仍可能发生舍入或下溢。空输入和实际零向量的处理方式保持不变。
+未增加非有限输入的校验或清理策略;修正有限中间结果也可能影响与非有限元素混合时的结果。
+
 向量函数不会消耗或修改输入向量缓冲区。同一个向量字段可以用于多个表达式,也可以同时作为距离函数的两个参数,并且仍可供下游转换和接收器读取。
 
 ### VECTOR_DIMS
diff --git 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/calcite_transform/calcite_builtin_functions.conf
 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/calcite_transform/calcite_builtin_functions.conf
index ead73d13f4..8fece4eb88 100644
--- 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/calcite_transform/calcite_builtin_functions.conf
+++ 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/calcite_transform/calcite_builtin_functions.conf
@@ -37,7 +37,19 @@ transform {
   Calcite {
     plugin_input = "fake"
     plugin_output = "calcite_out"
-    sql = "SELECT id, UPPER(name) AS name_upper, ABS(age) AS abs_age, 
COALESCE(name, 'unknown') AS safe_name FROM fake"
+    # Big-endian float encodings: Float.MAX_VALUE, -Float.MAX_VALUE and 
Float.MIN_VALUE.
+    sql = """SELECT id, UPPER(name) AS name_upper, ABS(age) AS abs_age, 
COALESCE(name, 'unknown') AS safe_name,
+      VECTOR_NORM(X'7F7FFFFF') > 3.0e38 AND VECTOR_NORM(X'7F7FFFFF') < 4.0e38 
AS large_norm,
+      INNER_PRODUCT(X'7F7FFFFF', X'7F7FFFFF') > 1.0e77 AND 
INNER_PRODUCT(X'7F7FFFFF', X'7F7FFFFF') < 2.0e77 AS large_product,
+      COSINE_DISTANCE(X'7F7FFFFF', X'FF7FFFFF') > 1.999 AND 
COSINE_DISTANCE(X'7F7FFFFF', X'FF7FFFFF') < 2.001 AS large_cosine,
+      L1_DISTANCE(X'7F7FFFFF', X'FF7FFFFF') > 6.0e38 AND 
L1_DISTANCE(X'7F7FFFFF', X'FF7FFFFF') < 7.0e38 AS large_l1,
+      L2_DISTANCE(X'7F7FFFFF', X'FF7FFFFF') > 6.0e38 AND 
L2_DISTANCE(X'7F7FFFFF', X'FF7FFFFF') < 7.0e38 AS large_l2,
+      VECTOR_NORM(X'00000001') > 1.0e-45 AND VECTOR_NORM(X'00000001') < 
2.0e-45 AS tiny_norm,
+      INNER_PRODUCT(X'00000001', X'00000001') > 1.0e-90 AND 
INNER_PRODUCT(X'00000001', X'00000001') < 3.0e-90 AS tiny_product,
+      COSINE_DISTANCE(X'00000001', X'00000001') > -0.001 AND 
COSINE_DISTANCE(X'00000001', X'00000001') < 0.001 AS tiny_cosine,
+      VECTOR_NORM(VECTOR_NORMALIZE(X'7F7FFFFF')) = 1.0 AS large_normalized,
+      VECTOR_NORM(VECTOR_NORMALIZE(X'00000001')) = 1.0 AS tiny_normalized
+    FROM fake"""
   }
 }
 
@@ -52,6 +64,16 @@ sink {
         }
       ]
       field_rules = [
+        {field_name = "large_norm", field_type = "boolean", field_value = 
[{equals_to = true}]},
+        {field_name = "large_product", field_type = "boolean", field_value = 
[{equals_to = true}]},
+        {field_name = "large_cosine", field_type = "boolean", field_value = 
[{equals_to = true}]},
+        {field_name = "large_l1", field_type = "boolean", field_value = 
[{equals_to = true}]},
+        {field_name = "large_l2", field_type = "boolean", field_value = 
[{equals_to = true}]},
+        {field_name = "tiny_norm", field_type = "boolean", field_value = 
[{equals_to = true}]},
+        {field_name = "tiny_product", field_type = "boolean", field_value = 
[{equals_to = true}]},
+        {field_name = "tiny_cosine", field_type = "boolean", field_value = 
[{equals_to = true}]},
+        {field_name = "large_normalized", field_type = "boolean", field_value 
= [{equals_to = true}]},
+        {field_name = "tiny_normalized", field_type = "boolean", field_value = 
[{equals_to = true}]},
         {
           field_name = name_upper
           field_type = string
diff --git 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/sql_transform/func_vector.conf
 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/sql_transform/func_vector.conf
index 5fd28c53fb..4c5879b0f2 100644
--- 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/sql_transform/func_vector.conf
+++ 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/sql_transform/func_vector.conf
@@ -25,6 +25,20 @@ env {
 }
 
 source {
+  FakeSource {
+    plugin_output = "arithmetic_input"
+    schema = {
+      fields {
+        large = "array<float>"
+        opposite = "array<float>"
+        tiny = "array<float>"
+      }
+    }
+    rows = [{
+      fields = [[3.0e38, -3.0e38], [-3.0e38, 3.0e38], [1.0e-30, -1.0e-30]]
+      kind = INSERT
+    }]
+  }
   FakeSource {
     plugin_output = "fake"
     schema = {
@@ -53,6 +67,22 @@ source {
 }
 
 transform {
+  Sql {
+    plugin_input = "arithmetic_input"
+    plugin_output = "arithmetic_result"
+    query = """SELECT
+      CASE WHEN VECTOR_NORM(large) > 4.0e38 AND VECTOR_NORM(large) < 5.0e38 
THEN 1 ELSE 0 END AS large_norm,
+      CASE WHEN INNER_PRODUCT(large, opposite) > -2.0e77 AND 
INNER_PRODUCT(large, opposite) < -1.0e77 THEN 1 ELSE 0 END AS large_product,
+      CASE WHEN COSINE_DISTANCE(large, opposite) > 1.999 AND 
COSINE_DISTANCE(large, opposite) < 2.001 THEN 1 ELSE 0 END AS large_cosine,
+      CASE WHEN L1_DISTANCE(large, opposite) > 1.0e39 AND L1_DISTANCE(large, 
opposite) < 1.3e39 THEN 1 ELSE 0 END AS large_l1,
+      CASE WHEN L2_DISTANCE(large, opposite) > 8.0e38 AND L2_DISTANCE(large, 
opposite) < 9.0e38 THEN 1 ELSE 0 END AS large_l2,
+      CASE WHEN VECTOR_NORM(tiny) > 1.0e-30 AND VECTOR_NORM(tiny) < 2.0e-30 
THEN 1 ELSE 0 END AS tiny_norm,
+      CASE WHEN INNER_PRODUCT(tiny, tiny) > 1.0e-60 AND INNER_PRODUCT(tiny, 
tiny) < 3.0e-60 THEN 1 ELSE 0 END AS tiny_product,
+      CASE WHEN COSINE_DISTANCE(tiny, tiny) > -0.001 AND COSINE_DISTANCE(tiny, 
tiny) < 0.001 THEN 1 ELSE 0 END AS tiny_cosine,
+      CASE WHEN VECTOR_NORM(VECTOR_NORMALIZE(large)) > 0.999 AND 
VECTOR_NORM(VECTOR_NORMALIZE(large)) < 1.001 THEN 1 ELSE 0 END AS 
large_normalized,
+      CASE WHEN VECTOR_NORM(VECTOR_NORMALIZE(tiny)) > 0.999 AND 
VECTOR_NORM(VECTOR_NORMALIZE(tiny)) < 1.001 THEN 1 ELSE 0 END AS tiny_normalized
+    FROM dual"""
+  }
   Sql {
     plugin_input = "fake"
     plugin_output = "vector_buffers"
@@ -78,6 +108,27 @@ transform {
 }
 
 sink {
+  Assert {
+    plugin_input = "arithmetic_result"
+    rules {
+      field_rules = [
+        {field_name = "large_norm", field_type = "int", field_value = 
[{equals_to = 1}]},
+        {field_name = "large_product", field_type = "int", field_value = 
[{equals_to = 1}]},
+        {field_name = "large_cosine", field_type = "int", field_value = 
[{equals_to = 1}]},
+        {field_name = "large_l1", field_type = "int", field_value = 
[{equals_to = 1}]},
+        {field_name = "large_l2", field_type = "int", field_value = 
[{equals_to = 1}]},
+        {field_name = "tiny_norm", field_type = "int", field_value = 
[{equals_to = 1}]},
+        {field_name = "tiny_product", field_type = "int", field_value = 
[{equals_to = 1}]},
+        {field_name = "tiny_cosine", field_type = "int", field_value = 
[{equals_to = 1}]},
+        {field_name = "large_normalized", field_type = "int", field_value = 
[{equals_to = 1}]},
+        {field_name = "tiny_normalized", field_type = "int", field_value = 
[{equals_to = 1}]}
+      ]
+      row_rules = [
+        {rule_type = MAX_ROW, rule_value = 1},
+        {rule_type = MIN_ROW, rule_value = 1}
+      ]
+    }
+  }
   Assert {
     plugin_input = "fake1"
     rules = {
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/CosineDistanceFunction.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/CosineDistanceFunction.java
index 9f19dabf2b..130b88c628 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/CosineDistanceFunction.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/CosineDistanceFunction.java
@@ -52,9 +52,11 @@ public class CosineDistanceFunction implements CalciteUdf {
                             vector1.length, vector2.length));
         }
         double dotProduct =
-                IntStream.range(0, vector1.length).mapToDouble(i -> vector1[i] 
* vector2[i]).sum();
-        double norm1 = Arrays.stream(vector1).mapToDouble(v -> v * v).sum();
-        double norm2 = Arrays.stream(vector2).mapToDouble(v -> v * v).sum();
+                IntStream.range(0, vector1.length)
+                        .mapToDouble(i -> (double) vector1[i] * vector2[i])
+                        .sum();
+        double norm1 = Arrays.stream(vector1).mapToDouble(v -> (double) v * 
v).sum();
+        double norm2 = Arrays.stream(vector2).mapToDouble(v -> (double) v * 
v).sum();
         if (norm1 == 0.0 || norm2 == 0.0) {
             return 1.0;
         }
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/InnerProductFunction.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/InnerProductFunction.java
index 3481b07dab..ec54ac51c9 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/InnerProductFunction.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/InnerProductFunction.java
@@ -49,6 +49,8 @@ public class InnerProductFunction implements CalciteUdf {
                             "Vectors must have the same dimension: %d vs %d",
                             vector1.length, vector2.length));
         }
-        return IntStream.range(0, vector1.length).mapToDouble(i -> vector1[i] 
* vector2[i]).sum();
+        return IntStream.range(0, vector1.length)
+                .mapToDouble(i -> (double) vector1[i] * vector2[i])
+                .sum();
     }
 }
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/L1DistanceFunction.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/L1DistanceFunction.java
index cd23ea03f6..1ce91d2ac7 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/L1DistanceFunction.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/L1DistanceFunction.java
@@ -50,7 +50,7 @@ public class L1DistanceFunction implements CalciteUdf {
                             vector1.length, vector2.length));
         }
         return IntStream.range(0, vector1.length)
-                .mapToDouble(i -> Math.abs(vector1[i] - vector2[i]))
+                .mapToDouble(i -> Math.abs((double) vector1[i] - vector2[i]))
                 .sum();
     }
 }
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/L2DistanceFunction.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/L2DistanceFunction.java
index 25fa89e46f..0a52e12a3b 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/L2DistanceFunction.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/L2DistanceFunction.java
@@ -53,7 +53,7 @@ public class L2DistanceFunction implements CalciteUdf {
                 IntStream.range(0, vector1.length)
                         .mapToDouble(
                                 i -> {
-                                    double diff = vector1[i] - vector2[i];
+                                    double diff = (double) vector1[i] - 
vector2[i];
                                     return diff * diff;
                                 })
                         .sum();
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/VectorNormFunction.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/VectorNormFunction.java
index 4288b1839c..04a04857a9 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/VectorNormFunction.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/VectorNormFunction.java
@@ -42,6 +42,6 @@ public class VectorNormFunction implements CalciteUdf {
             return null;
         }
         Float[] vector = VectorUtils.toFloatArray(ByteBuffer.wrap(v));
-        return Math.sqrt(Arrays.stream(vector).mapToDouble(val -> val * 
val).sum());
+        return Math.sqrt(Arrays.stream(vector).mapToDouble(val -> (double) val 
* val).sum());
     }
 }
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/VectorNormalizeFunction.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/VectorNormalizeFunction.java
index 9ef0f4702f..f42a20c3d6 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/VectorNormalizeFunction.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/calcite/udf/VectorNormalizeFunction.java
@@ -44,7 +44,7 @@ public class VectorNormalizeFunction implements CalciteUdf {
         double magnitude = 0.0;
         for (Float value : vector) {
             if (value != null) {
-                magnitude += value * value;
+                magnitude += (double) value * value;
             }
         }
         magnitude = Math.sqrt(magnitude);
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/VectorFunction.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/VectorFunction.java
index d75dc21534..cddabf0286 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/VectorFunction.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/VectorFunction.java
@@ -55,9 +55,11 @@ public class VectorFunction {
                             vector1.length, vector2.length));
         }
         double dotProduct =
-                IntStream.range(0, vector1.length).mapToDouble(i -> vector1[i] 
* vector2[i]).sum();
-        double norm1 = Arrays.stream(vector1).mapToDouble(v -> v * v).sum();
-        double norm2 = Arrays.stream(vector2).mapToDouble(v -> v * v).sum();
+                IntStream.range(0, vector1.length)
+                        .mapToDouble(i -> (double) vector1[i] * vector2[i])
+                        .sum();
+        double norm1 = Arrays.stream(vector1).mapToDouble(v -> (double) v * 
v).sum();
+        double norm2 = Arrays.stream(vector2).mapToDouble(v -> (double) v * 
v).sum();
         if (norm1 == 0.0 || norm2 == 0.0) {
             return 1.0;
         }
@@ -88,7 +90,9 @@ public class VectorFunction {
                             "Vectors must have the same dimension: %d vs %d",
                             v1.length, v2.length));
         }
-        return IntStream.range(0, v1.length).mapToDouble(i -> Math.abs(v1[i] - 
v2[i])).sum();
+        return IntStream.range(0, v1.length)
+                .mapToDouble(i -> Math.abs((double) v1[i] - v2[i]))
+                .sum();
     }
 
     public static Object l2Distance(List<Object> args) {
@@ -117,7 +121,7 @@ public class VectorFunction {
                 IntStream.range(0, v1.length)
                         .mapToDouble(
                                 i -> {
-                                    double diff = v1[i] - v2[i];
+                                    double diff = (double) v1[i] - v2[i];
                                     return diff * diff;
                                 })
                         .sum();
@@ -153,7 +157,7 @@ public class VectorFunction {
             return null;
         }
         Float[] vector = convertToFloatArray(arg);
-        return Math.sqrt(Arrays.stream(vector).mapToDouble(v -> v * v).sum());
+        return Math.sqrt(Arrays.stream(vector).mapToDouble(v -> (double) v * 
v).sum());
     }
 
     public static Object innerProduct(List<Object> args) {
@@ -179,7 +183,7 @@ public class VectorFunction {
                             v1.length, v2.length));
         }
 
-        return IntStream.range(0, v1.length).mapToDouble(i -> v1[i] * 
v2[i]).sum();
+        return IntStream.range(0, v1.length).mapToDouble(i -> (double) v1[i] * 
v2[i]).sum();
     }
 
     private static Float[] convertToFloatArray(Object obj) {
@@ -292,7 +296,7 @@ public class VectorFunction {
         double magnitude = 0.0;
         for (Float value : vector) {
             if (value != null) {
-                magnitude += value * value;
+                magnitude += (double) value * value;
             }
         }
         magnitude = Math.sqrt(magnitude);
diff --git 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/calcite/CalciteSQLEngineTest.java
 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/calcite/CalciteSQLEngineTest.java
index efbb2e43e7..19ff74249a 100644
--- 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/calcite/CalciteSQLEngineTest.java
+++ 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/calcite/CalciteSQLEngineTest.java
@@ -4057,6 +4057,42 @@ class CalciteSQLEngineTest {
                 });
     }
 
+    @Test
+    void testFiniteVectorArithmetic() {
+        double value = Float.MAX_VALUE;
+        String[] expressions = {
+            "VECTOR_NORM(vec1)",
+            "INNER_PRODUCT(vec1, vec2)",
+            "COSINE_DISTANCE(vec1, vec2)",
+            "L1_DISTANCE(vec1, vec2)",
+            "L2_DISTANCE(vec1, vec2)",
+            "VECTOR_NORM(VECTOR_NORMALIZE(vec1))"
+        };
+        double[] expected = {value, -value * value, 2.0, 2 * value, 2 * value, 
1.0};
+        for (int i = 0; i < expressions.length; i++) {
+            CalciteSQLEngine engine =
+                    createAndInit(
+                            "SELECT " + expressions[i] + " AS vector_result 
FROM t",
+                            "t",
+                            twoVectorRowType());
+            try {
+                Object actual =
+                        singleField(
+                                engine,
+                                new Object[] {
+                                    floatVec(Float.MAX_VALUE), 
floatVec(-Float.MAX_VALUE)
+                                });
+                Assertions.assertEquals(
+                        expected[i],
+                        ((Number) actual).doubleValue(),
+                        Math.abs(expected[i]) * 1e-14,
+                        expressions[i]);
+            } finally {
+                engine.close();
+            }
+        }
+    }
+
     private SeaTunnelRowType singleVectorRowType() {
         return new SeaTunnelRowType(
                 new String[] {"vec"}, new SeaTunnelDataType[] 
{VectorType.VECTOR_FLOAT_TYPE});
diff --git 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/calcite/udf/VectorUdfTest.java
 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/calcite/udf/VectorUdfTest.java
index 519a717515..36242cfdf3 100644
--- 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/calcite/udf/VectorUdfTest.java
+++ 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/calcite/udf/VectorUdfTest.java
@@ -21,11 +21,80 @@ import org.apache.seatunnel.common.utils.VectorUtils;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
+import java.math.BigDecimal;
 import java.nio.ByteBuffer;
 
 class VectorUdfTest {
 
+    @ParameterizedTest
+    @ValueSource(
+            floats = {Float.MAX_VALUE, 1e20f, 1e-30f, Float.MIN_NORMAL, 
Float.MIN_VALUE, 3f, 0.1f})
+    void testFiniteVectorArithmetic(float value) {
+        byte[] left = toBytes(new Float[] {value, -value});
+        byte[] right = toBytes(new Float[] {-value, value});
+        BigDecimal exact = new BigDecimal((double) value);
+        double squaredNorm = 
exact.multiply(exact).multiply(BigDecimal.valueOf(2)).doubleValue();
+        double norm = Math.hypot(value, value);
+        double distance = exact.multiply(BigDecimal.valueOf(4)).doubleValue();
+        Assertions.assertAll(
+                () -> Assertions.assertEquals(norm, 
VectorNormFunction.eval(left), norm * 1e-14),
+                () ->
+                        Assertions.assertEquals(
+                                -squaredNorm,
+                                InnerProductFunction.eval(left, right),
+                                squaredNorm * 1e-14),
+                () -> Assertions.assertEquals(2.0, 
CosineDistanceFunction.eval(left, right), 1e-14),
+                () ->
+                        Assertions.assertEquals(
+                                distance, L1DistanceFunction.eval(left, 
right), distance * 1e-14),
+                () ->
+                        Assertions.assertEquals(
+                                2 * norm, L2DistanceFunction.eval(left, 
right), norm * 1e-14),
+                () -> {
+                    Float[] normalized = 
fromBytes(VectorNormalizeFunction.eval(left));
+                    Assertions.assertEquals(1 / Math.sqrt(2), normalized[0], 
1e-7);
+                    Assertions.assertEquals(-1 / Math.sqrt(2), normalized[1], 
1e-7);
+                });
+    }
+
+    @Test
+    void testLargeProductsCancelWithoutOverflow() {
+        byte[] left = toBytes(new Float[] {Float.MAX_VALUE, Float.MAX_VALUE});
+        byte[] right = toBytes(new Float[] {Float.MAX_VALUE, 
-Float.MAX_VALUE});
+        Assertions.assertEquals(0.0, InnerProductFunction.eval(left, right));
+        Assertions.assertEquals(1.0, CosineDistanceFunction.eval(left, right));
+    }
+
+    @Test
+    void testVectorArithmeticSpecialValues() {
+        byte[] zero = toBytes(new Float[] {0f, -0f});
+        byte[] empty = toBytes(new Float[] {});
+        Assertions.assertEquals(0.0, VectorNormFunction.eval(zero));
+        Assertions.assertEquals(1.0, CosineDistanceFunction.eval(zero, zero));
+        Assertions.assertEquals(1.0, CosineDistanceFunction.eval(empty, 
empty));
+        Assertions.assertSame(zero, VectorNormalizeFunction.eval(zero));
+        Assertions.assertSame(empty, VectorNormalizeFunction.eval(empty));
+        Assertions.assertTrue(
+                Double.isNaN(
+                        CosineDistanceFunction.eval(
+                                toBytes(new Float[] {Float.MIN_VALUE}),
+                                toBytes(new Float[] 
{Float.POSITIVE_INFINITY}))));
+        for (float value :
+                new float[] {Float.NaN, Float.POSITIVE_INFINITY, 
Float.NEGATIVE_INFINITY}) {
+            byte[] vector = toBytes(new Float[] {value});
+            
Assertions.assertTrue(Double.isNaN(CosineDistanceFunction.eval(vector, 
vector)));
+            Assertions.assertTrue(Double.isNaN(L1DistanceFunction.eval(vector, 
vector)));
+            Assertions.assertTrue(Double.isNaN(L2DistanceFunction.eval(vector, 
vector)));
+            double norm = VectorNormFunction.eval(vector);
+            Assertions.assertTrue(
+                    Float.isNaN(value) ? Double.isNaN(norm) : norm == 
Double.POSITIVE_INFINITY);
+            
Assertions.assertTrue(Float.isNaN(fromBytes(VectorNormalizeFunction.eval(vector))[0]));
+        }
+    }
+
     private static byte[] toBytes(Float[] floats) {
         ByteBuffer buf = VectorUtils.toByteBuffer(floats);
         byte[] bytes = new byte[buf.remaining()];
diff --git 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/SQLVectorFunctionTest.java
 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/SQLVectorFunctionTest.java
index 3e512e3b29..7fe0c50c09 100644
--- 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/SQLVectorFunctionTest.java
+++ 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/SQLVectorFunctionTest.java
@@ -46,6 +46,41 @@ public class SQLVectorFunctionTest {
             new String[] {"id", "vector_field", "vector_field2"};
     private CatalogTable catalogTable;
 
+    @Test
+    public void testFiniteVectorArithmeticThroughSql() {
+        double value = Float.MAX_VALUE;
+        String[] expressions = {
+            "VECTOR_NORM(vector_field)",
+            "INNER_PRODUCT(vector_field, vector_field2)",
+            "COSINE_DISTANCE(vector_field, vector_field2)",
+            "L1_DISTANCE(vector_field, vector_field2)",
+            "L2_DISTANCE(vector_field, vector_field2)",
+            "VECTOR_NORM(VECTOR_NORMALIZE(vector_field))"
+        };
+        double[] expected = {value, -value * value, 2.0, 2 * value, 2 * value, 
1.0};
+        for (int i = 0; i < expressions.length; i++) {
+            SQLTransform transform =
+                    new SQLTransform(
+                            ReadonlyConfig.fromMap(
+                                    Collections.singletonMap(
+                                            "query",
+                                            "SELECT " + expressions[i] + " AS 
result FROM dual")),
+                            catalogTable);
+            SeaTunnelRow input =
+                    new SeaTunnelRow(
+                            new Object[] {
+                                1,
+                                VectorUtils.toByteBuffer(new Float[] 
{Float.MAX_VALUE}),
+                                VectorUtils.toByteBuffer(new Float[] 
{-Float.MAX_VALUE})
+                            });
+            Assertions.assertEquals(
+                    expected[i],
+                    (double) transform.transformRow(input).get(0).getField(0),
+                    Math.abs(expected[i]) * 1e-14,
+                    expressions[i]);
+        }
+    }
+
     @BeforeEach
     void setUp() {
         SeaTunnelRowType rowType =
diff --git 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/VectorFunctionTest.java
 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/VectorFunctionTest.java
index a5d3fc705e..9c4672ffe3 100644
--- 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/VectorFunctionTest.java
+++ 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/VectorFunctionTest.java
@@ -31,7 +31,9 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
+import java.math.BigDecimal;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -43,6 +45,101 @@ import java.util.stream.Stream;
 
 public class VectorFunctionTest {
 
+    @ParameterizedTest
+    @ValueSource(
+            floats = {Float.MAX_VALUE, 1e20f, 1e-30f, Float.MIN_NORMAL, 
Float.MIN_VALUE, 3f, 0.1f})
+    public void testFiniteVectorArithmetic(float value) {
+        Float[] left = {value, -value};
+        Float[] right = {-value, value};
+        BigDecimal exact = new BigDecimal((double) value);
+        double squaredNorm = 
exact.multiply(exact).multiply(BigDecimal.valueOf(2)).doubleValue();
+        double norm = Math.hypot(value, value);
+        double distance = exact.multiply(BigDecimal.valueOf(4)).doubleValue();
+        Assertions.assertAll(
+                () ->
+                        Assertions.assertEquals(
+                                norm,
+                                (double) 
VectorFunction.vectorNorm(Collections.singletonList(left)),
+                                norm * 1e-14),
+                () ->
+                        Assertions.assertEquals(
+                                -squaredNorm,
+                                (double) 
VectorFunction.innerProduct(Arrays.asList(left, right)),
+                                squaredNorm * 1e-14),
+                () ->
+                        Assertions.assertEquals(
+                                2.0,
+                                (double) 
VectorFunction.cosineDistance(Arrays.asList(left, right)),
+                                1e-14),
+                () ->
+                        Assertions.assertEquals(
+                                distance,
+                                (double) 
VectorFunction.l1Distance(Arrays.asList(left, right)),
+                                distance * 1e-14),
+                () ->
+                        Assertions.assertEquals(
+                                2 * norm,
+                                (double) 
VectorFunction.l2Distance(Arrays.asList(left, right)),
+                                norm * 1e-14),
+                () -> {
+                    Float[] normalized =
+                            VectorUtils.toFloatArray(
+                                    (ByteBuffer) 
VectorFunction.vectorNormalize(left));
+                    Assertions.assertEquals(1 / Math.sqrt(2), normalized[0], 
1e-7);
+                    Assertions.assertEquals(-1 / Math.sqrt(2), normalized[1], 
1e-7);
+                });
+    }
+
+    @Test
+    public void testLargeProductsCancelWithoutOverflow() {
+        Float[] left = {Float.MAX_VALUE, Float.MAX_VALUE};
+        Float[] right = {Float.MAX_VALUE, -Float.MAX_VALUE};
+        Assertions.assertEquals(0.0, 
VectorFunction.innerProduct(Arrays.asList(left, right)));
+        Assertions.assertEquals(1.0, 
VectorFunction.cosineDistance(Arrays.asList(left, right)));
+    }
+
+    @Test
+    public void testVectorArithmeticSpecialValues() {
+        Float[] zero = {0f, -0f};
+        Float[] empty = {};
+        Assertions.assertEquals(0.0, 
VectorFunction.vectorNorm(Collections.singletonList(zero)));
+        Assertions.assertEquals(1.0, 
VectorFunction.cosineDistance(Arrays.asList(zero, zero)));
+        Assertions.assertEquals(1.0, 
VectorFunction.cosineDistance(Arrays.asList(empty, empty)));
+        Assertions.assertSame(zero, VectorFunction.vectorNormalize(zero));
+        Assertions.assertSame(empty, VectorFunction.vectorNormalize(empty));
+        Assertions.assertTrue(
+                Double.isNaN(
+                        (double)
+                                VectorFunction.cosineDistance(
+                                        Arrays.asList(
+                                                new Float[] {Float.MIN_VALUE},
+                                                new Float[] 
{Float.POSITIVE_INFINITY}))));
+        
Assertions.assertNull(VectorFunction.vectorNorm(Collections.singletonList(null)));
+        
Assertions.assertNull(VectorFunction.cosineDistance(Arrays.asList(zero, null)));
+        Assertions.assertNull(VectorFunction.innerProduct(Arrays.asList(null, 
zero)));
+        Assertions.assertNull(VectorFunction.l1Distance(Arrays.asList(zero, 
null)));
+        Assertions.assertNull(VectorFunction.l2Distance(Arrays.asList(null, 
zero)));
+        for (float value :
+                new float[] {Float.NaN, Float.POSITIVE_INFINITY, 
Float.NEGATIVE_INFINITY}) {
+            Float[] vector = {value};
+            Assertions.assertTrue(
+                    Double.isNaN(
+                            (double) 
VectorFunction.cosineDistance(Arrays.asList(vector, vector))));
+            Assertions.assertTrue(
+                    Double.isNaN(
+                            (double) 
VectorFunction.l1Distance(Arrays.asList(vector, vector))));
+            Assertions.assertTrue(
+                    Double.isNaN(
+                            (double) 
VectorFunction.l2Distance(Arrays.asList(vector, vector))));
+            double norm = (double) 
VectorFunction.vectorNorm(Collections.singletonList(vector));
+            Assertions.assertTrue(
+                    Float.isNaN(value) ? Double.isNaN(norm) : norm == 
Double.POSITIVE_INFINITY);
+            Float[] normalized =
+                    VectorUtils.toFloatArray((ByteBuffer) 
VectorFunction.vectorNormalize(vector));
+            Assertions.assertTrue(Float.isNaN(normalized[0]));
+        }
+    }
+
     @ParameterizedTest
     @MethodSource("vectorBuffers")
     public void testVectorReadsPreserveBuffer(ByteBuffer buffer) {

Reply via email to