This is an automated email from the ASF dual-hosted git repository.

davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new e21e4f7ed2 [Feature][Connector-V2][Milvus] Support scalar field write 
null (#11216)
e21e4f7ed2 is described below

commit e21e4f7ed2b3ae60d7e6d33fb16662f0375a9bfe
Author: loupipalien <[email protected]>
AuthorDate: Sun Jul 12 20:16:28 2026 +0800

    [Feature][Connector-V2][Milvus] Support scalar field write null (#11216)
---
 docs/en/connectors/sink/Milvus.md                  |   1 +
 docs/zh/connectors/sink/Milvus.md                  |   1 +
 .../seatunnel/milvus/catalog/MilvusCatalog.java    |   7 +-
 .../seatunnel/milvus/config/MilvusSinkOptions.java |   6 +
 .../seatunnel/milvus/sink/MilvusSinkFactory.java   |   1 +
 .../milvus/utils/sink/MilvusSinkConverter.java     |  77 +++++-
 .../milvus/utils/source/MilvusSourceConverter.java |   1 +
 .../milvus/catalog/MilvusCatalogTest.java          |  16 ++
 .../milvus/sink/MilvusSinkFactoryTest.java         |   1 +
 .../milvus/utils/sink/MilvusSinkConverterTest.java | 261 +++++++++++++++++++--
 .../utils/source/MilvusSourceConverterTest.java    |  44 ++++
 .../e2e/connector/v2/milvus/MilvusIT.java          |  89 ++++++-
 .../test/resources/fake-to-milvus-nullable.conf    |  73 ++++++
 .../seatunnel/format/json/JsonToRowConverters.java |  29 +++
 .../format/json/JsonRowDataSerDeSchemaTest.java    |  35 +++
 15 files changed, 617 insertions(+), 25 deletions(-)

diff --git a/docs/en/connectors/sink/Milvus.md 
b/docs/en/connectors/sink/Milvus.md
index 832bb9dae1..59abb90a3c 100644
--- a/docs/en/connectors/sink/Milvus.md
+++ b/docs/en/connectors/sink/Milvus.md
@@ -59,6 +59,7 @@ Common use cases:
 | enable_auto_id         | boolean             | No       | false              
          | Whether Milvus should generate primary key values automatically. If 
this is `true`, do not send values for the primary key field.                   
                                                                              |
 | enable_upsert          | boolean             | No       | true               
          | Whether to write by upsert instead of insert. Upsert needs a 
primary key in the collection schema.                                           
                                                                                
    |
 | enable_dynamic_field   | boolean             | No       | true               
          | Whether to enable Milvus dynamic fields when SeaTunnel creates a 
collection.                                                                     
                                                                                
|
+| enable_nullable_field  | boolean             | No       | false              
          | Enable nullable fields. Requires Milvus 2.5 or later. When enabled, 
SeaTunnel creates the field as a Milvus nullable field and allows writing 
`null` values when the SeaTunnel column is nullable and the field is used as a 
Milvus scalar field. |
 | batch_size             | int                 | No       | 1000               
          | Number of records buffered before one write. In streaming jobs, 
data can also be flushed when a checkpoint is triggered.                        
                                                                                
 |
 | rate_limit             | int                 | No       | 100000             
          | Milvus collection write-rate limit in MB/s. When greater than 0, 
the connector sets `collection.insertRate.max.mb` and 
`collection.upsertRate.max.mb` while the writer is running.                     
                        |
 | partition_key          | String              | No       | -                  
          | Field name used as the Milvus partition key when SeaTunnel creates 
a collection.                                                                   
                                                                              |
diff --git a/docs/zh/connectors/sink/Milvus.md 
b/docs/zh/connectors/sink/Milvus.md
index bc4fdb03f6..59f192054b 100644
--- a/docs/zh/connectors/sink/Milvus.md
+++ b/docs/zh/connectors/sink/Milvus.md
@@ -59,6 +59,7 @@ SeaTunnel 表结构创建目标集合,并支持向量字段、动态字段、
 | enable_auto_id         | boolean             | 否    | false                  
      | 是否让 Milvus 自动生成主键。设置为 `true` 时,不要再向主键字段写值。                              
            |
 | enable_upsert          | boolean             | 否    | true                   
      | 是否使用更新插入模式写入,而不是普通插入模式。更新插入需要集合中有主键。                                    
         |
 | enable_dynamic_field   | boolean             | 否    | true                   
      | SeaTunnel 创建集合时,是否启用 Milvus 动态字段。                                       
                |
+| enable_nullable_field  | boolean             | 否    | false                  
      | 是否启用可空字段。需要 Milvus 2.5 或更高版本。开启后,当 SeaTunnel 列定义为 nullable 且作为 Milvus 
标量字段,SeaTunnel 会将该字段创建为 Milvus nullable 字段并允许写入 `null` 值。|
 | batch_size             | int                 | 否    | 1000                   
      | 写入前缓存的记录数。流任务中,检查点触发时也会刷新写入。                                            
         |
 | rate_limit             | int                 | 否    | 100000                 
      | Milvus collection 写入限速,单位 MB/s。大于 0 时,写入器运行期间会设置 
`collection.insertRate.max.mb` 和 `collection.upsertRate.max.mb`。 |
 | partition_key          | String              | 否    | -                      
      | SeaTunnel 创建集合时使用的 Milvus 分区键字段名。                                       
                |
diff --git 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalog.java
 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalog.java
index d4860e9c0d..9ab904fa87 100644
--- 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalog.java
+++ 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalog.java
@@ -160,6 +160,10 @@ public class MilvusCatalog implements Catalog {
 
     @Override
     public boolean tableExists(TablePath tablePath) throws CatalogException {
+        if (!databaseExists(tablePath.getDatabaseName())) {
+            return false;
+        }
+
         R<Boolean> response =
                 this.client.hasCollection(
                         HasCollectionParam.newBuilder()
@@ -302,7 +306,8 @@ public class MilvusCatalog implements Catalog {
                                 column,
                                 tableSchema.getPrimaryKey(),
                                 partitionKeyField,
-                                config.get(MilvusSinkOptions.ENABLE_AUTO_ID));
+                                config.get(MilvusSinkOptions.ENABLE_AUTO_ID),
+                                
config.get(MilvusSinkOptions.ENABLE_NULLABLE_FIELD));
                 fieldTypes.add(fieldType);
             }
 
diff --git 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSinkOptions.java
 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSinkOptions.java
index 4113be01b8..75ac1d922d 100644
--- 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSinkOptions.java
+++ 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSinkOptions.java
@@ -77,6 +77,12 @@ public class MilvusSinkOptions extends MilvusBaseOptions {
                     .defaultValue(true)
                     .withDescription("Enable dynamic field");
 
+    public static final Option<Boolean> ENABLE_NULLABLE_FIELD =
+            Options.key("enable_nullable_field")
+                    .booleanType()
+                    .defaultValue(false)
+                    .withDescription("Enable nullable field");
+
     public static final Option<Integer> BATCH_SIZE =
             Options.key("batch_size")
                     .intType()
diff --git 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkFactory.java
 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkFactory.java
index 8cfab9d139..f1ed7fe122 100644
--- 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkFactory.java
+++ 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkFactory.java
@@ -50,6 +50,7 @@ public class MilvusSinkFactory implements TableSinkFactory {
                         MilvusSinkOptions.PARTITION_KEY,
                         MilvusSinkOptions.ENABLE_UPSERT,
                         MilvusSinkOptions.ENABLE_DYNAMIC_FIELD,
+                        MilvusSinkOptions.ENABLE_NULLABLE_FIELD,
                         MilvusSinkOptions.ENABLE_AUTO_ID,
                         MilvusSinkOptions.BATCH_SIZE,
                         MilvusSinkOptions.RATE_LIMIT,
diff --git 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverter.java
 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverter.java
index ae7fb816f9..e9f5d26cfc 100644
--- 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverter.java
+++ 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverter.java
@@ -32,10 +32,12 @@ import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
 import org.apache.seatunnel.api.table.type.SqlType;
 import org.apache.seatunnel.common.utils.JsonUtils;
 import org.apache.seatunnel.common.utils.VectorUtils;
+import org.apache.seatunnel.connectors.seatunnel.milvus.catalog.MilvusOptions;
 import 
org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode;
 import 
org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonNull;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import io.milvus.grpc.DataType;
@@ -50,6 +52,8 @@ import java.util.stream.Collectors;
 import static 
org.apache.seatunnel.api.table.catalog.PrimaryKey.isPrimaryKeyField;
 import static 
org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkOptions.ENABLE_AUTO_ID;
 import static 
org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkOptions.ENABLE_DYNAMIC_FIELD;
+import static 
org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkOptions.ENABLE_NULLABLE_FIELD;
+import static 
org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkOptions.PARTITION_KEY;
 
 public class MilvusSinkConverter {
     private static final Gson gson = new Gson();
@@ -126,7 +130,11 @@ public class MilvusSinkConverter {
     }
 
     public static FieldType convertToFieldType(
-            Column column, PrimaryKey primaryKey, String partitionKeyField, 
Boolean autoId) {
+            Column column,
+            PrimaryKey primaryKey,
+            String partitionKeyField,
+            Boolean autoId,
+            Boolean enableNullableField) {
         SeaTunnelDataType<?> seaTunnelDataType = column.getDataType();
         DataType milvusDataType;
         if (column.getSinkType() != null) {
@@ -135,7 +143,15 @@ public class MilvusSinkConverter {
             milvusDataType = 
convertSqlTypeToDataType(seaTunnelDataType.getSqlType());
         }
         FieldType.Builder build =
-                
FieldType.newBuilder().withName(column.getName()).withDataType(milvusDataType);
+                FieldType.newBuilder()
+                        .withName(column.getName())
+                        .withDataType(milvusDataType)
+                        .withNullable(
+                                allowNullableField(
+                                        column,
+                                        primaryKey,
+                                        partitionKeyField,
+                                        enableNullableField));
         if (StringUtils.isNotEmpty(column.getComment())) {
             build.withDescription(column.getComment());
         }
@@ -210,6 +226,36 @@ public class MilvusSinkConverter {
         return build.build();
     }
 
+    private static boolean allowNullableField(
+            Column column,
+            PrimaryKey primaryKey,
+            String partitionKeyField,
+            Boolean enableNullableField) {
+        return Boolean.TRUE.equals(enableNullableField)
+                && column.isNullable()
+                && supportNullableField(column, primaryKey, partitionKeyField);
+    }
+
+    private static boolean supportNullableField(
+            Column column, PrimaryKey primaryKey, String partitionKeyField) {
+        if (isPrimaryKeyField(primaryKey, column.getName())) {
+            return false;
+        }
+        if (StringUtils.equals(column.getName(), partitionKeyField)) {
+            return false;
+        }
+        switch (column.getDataType().getSqlType()) {
+            case FLOAT_VECTOR:
+            case BINARY_VECTOR:
+            case FLOAT16_VECTOR:
+            case BFLOAT16_VECTOR:
+            case SPARSE_FLOAT_VECTOR:
+                return false;
+            default:
+                return true;
+        }
+    }
+
     public static DataType convertSqlTypeToDataType(SqlType sqlType) {
         switch (sqlType) {
             case BOOLEAN:
@@ -260,6 +306,8 @@ public class MilvusSinkConverter {
         SeaTunnelRowType seaTunnelRowType = catalogTable.getSeaTunnelRowType();
         PrimaryKey primaryKey = catalogTable.getTableSchema().getPrimaryKey();
         Boolean autoId = config.get(ENABLE_AUTO_ID);
+        Boolean enableNullableField = config.get(ENABLE_NULLABLE_FIELD);
+        String partitionKeyField = getPartitionKeyField(catalogTable, config);
 
         JsonObject data = new JsonObject();
         Gson gson = new Gson();
@@ -273,9 +321,20 @@ public class MilvusSinkConverter {
 
             SeaTunnelDataType<?> fieldType = seaTunnelRowType.getFieldType(i);
             Object value = element.getField(i);
-            if (null == value) {
-                throw new MilvusConnectorException(
-                        MilvusConnectionErrorCode.FIELD_IS_NULL, fieldName);
+            if (value == null) {
+                Column column = 
catalogTable.getTableSchema().getColumn(fieldName);
+                if (!allowNullableField(
+                        column, primaryKey, partitionKeyField, 
enableNullableField)) {
+                    throw new MilvusConnectorException(
+                            MilvusConnectionErrorCode.FIELD_IS_NULL, 
fieldName);
+                }
+                if (dynamicField != null
+                        && dynamicField.equals(fieldName)
+                        && config.get(ENABLE_DYNAMIC_FIELD)) {
+                    continue;
+                }
+                data.add(fieldName, JsonNull.INSTANCE);
+                continue;
             }
             // if the field is dynamic field, then parse the dynamic field
             if (dynamicField != null
@@ -295,4 +354,12 @@ public class MilvusSinkConverter {
         }
         return data;
     }
+
+    private String getPartitionKeyField(CatalogTable catalogTable, 
ReadonlyConfig config) {
+        String partitionKeyField = 
catalogTable.getOptions().get(MilvusOptions.PARTITION_KEY_FIELD);
+        if (StringUtils.isNotEmpty(config.get(PARTITION_KEY))) {
+            partitionKeyField = config.get(PARTITION_KEY);
+        }
+        return partitionKeyField;
+    }
 }
diff --git 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverter.java
 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverter.java
index 6308e3a343..dd4ed46686 100644
--- 
a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverter.java
+++ 
b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverter.java
@@ -262,6 +262,7 @@ public class MilvusSourceConverter {
         PhysicalColumn.PhysicalColumnBuilder builder = 
PhysicalColumn.builder();
         builder.name(fieldSchema.getName());
         builder.sourceType(dataType.name());
+        builder.nullable(fieldSchema.getNullable());
         builder.comment(fieldSchema.getDescription());
 
         switch (dataType) {
diff --git 
a/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalogTest.java
 
b/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalogTest.java
index 1116cafe56..ae0d3c53bc 100644
--- 
a/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalogTest.java
+++ 
b/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalogTest.java
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import io.milvus.client.MilvusServiceClient;
+import io.milvus.grpc.ListDatabasesResponse;
 import io.milvus.grpc.ShowPartitionsResponse;
 import io.milvus.param.R;
 import io.milvus.param.RpcStatus;
@@ -47,6 +48,21 @@ import static org.mockito.Mockito.when;
 
 class MilvusCatalogTest {
 
+    @Test
+    void tableExistsReturnsFalseWhenDatabaseDoesNotExist() throws Exception {
+        MilvusServiceClient client = mock(MilvusServiceClient.class);
+        @SuppressWarnings("unchecked")
+        R<ListDatabasesResponse> listDatabasesR = mock(R.class);
+        when(listDatabasesR.getData())
+                
.thenReturn(ListDatabasesResponse.newBuilder().addDbNames("default").build());
+        when(client.listDatabases()).thenReturn(listDatabasesR);
+
+        MilvusCatalog catalog = createCatalogWithClient(client);
+        Assertions.assertFalse(catalog.tableExists(TablePath.of("missing_db", 
null, "coll")));
+
+        verify(client, never()).hasCollection(any());
+    }
+
     @Test
     void createPartitionInternalSkipsEmptyString() throws Exception {
         MilvusCatalog catalog = 
createCatalogWithClient(mockClientWithDefaultPartitions());
diff --git 
a/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkFactoryTest.java
 
b/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkFactoryTest.java
index 1b5542366e..ef7cd9f4d0 100644
--- 
a/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkFactoryTest.java
+++ 
b/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkFactoryTest.java
@@ -105,6 +105,7 @@ public class MilvusSinkFactoryTest {
         
assertTrue(declaredOptions.contains(MilvusSinkOptions.LOAD_COLLECTION));
         assertTrue(declaredOptions.contains(MilvusSinkOptions.CREATE_INDEX));
         assertTrue(declaredOptions.contains(MilvusSinkOptions.RATE_LIMIT));
+        
assertTrue(declaredOptions.contains(MilvusSinkOptions.ENABLE_NULLABLE_FIELD));
     }
 
     private SeaTunnelSink<?, ?, ?, ?> createSeaTunnelSink(Map<String, Object> 
options) {
diff --git 
a/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverterTest.java
 
b/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverterTest.java
index 3e94609255..95effd3618 100644
--- 
a/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverterTest.java
+++ 
b/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverterTest.java
@@ -17,52 +17,277 @@
 
 package org.apache.seatunnel.connectors.seatunnel.milvus.utils.sink;
 
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
 import org.apache.seatunnel.api.table.catalog.Column;
+import org.apache.seatunnel.api.table.catalog.PhysicalColumn;
+import org.apache.seatunnel.api.table.catalog.PrimaryKey;
+import org.apache.seatunnel.api.table.catalog.TableIdentifier;
+import org.apache.seatunnel.api.table.catalog.TableSchema;
 import org.apache.seatunnel.api.table.type.BasicType;
-import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
+import org.apache.seatunnel.api.table.type.CommonOptions;
+import org.apache.seatunnel.api.table.type.SeaTunnelRow;
+import org.apache.seatunnel.api.table.type.VectorType;
+import 
org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkOptions;
+import 
org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException;
 
 import org.junit.jupiter.api.Test;
 
+import com.google.gson.JsonNull;
 import io.milvus.grpc.DataType;
 import io.milvus.param.collection.FieldType;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class MilvusSinkConverterTest {
 
     @Test
     void returnsReconvertedTypeWhenSinkTypeNotNull() {
-        Column column = mock(Column.class);
-        when(column.getName()).thenReturn("col1");
-        when(column.getDataType()).thenReturn((SeaTunnelDataType) 
BasicType.SHORT_TYPE);
-        when(column.getSinkType()).thenReturn("Int64");
+        Column column = columnWithSinkType("col1", "Int64");
 
-        FieldType result = MilvusSinkConverter.convertToFieldType(column, 
null, null, null);
+        FieldType result = MilvusSinkConverter.convertToFieldType(column, 
null, null, null, false);
 
         assertEquals(DataType.Int64, result.getDataType());
     }
 
     @Test
     void returnsReconvertedTypeWhenSinkTypeIsNull() {
-        Column column = mock(Column.class);
-        when(column.getSinkType()).thenReturn(null);
-        when(column.getDataType()).thenReturn((SeaTunnelDataType) 
BasicType.SHORT_TYPE);
-        when(column.getName()).thenReturn("col1");
-        FieldType result = MilvusSinkConverter.convertToFieldType(column, 
null, null, null);
+        Column column = PhysicalColumn.of("col1", BasicType.SHORT_TYPE, 0L, 
true, null, "");
+        FieldType result = MilvusSinkConverter.convertToFieldType(column, 
null, null, null, false);
 
         assertEquals(DataType.Int16, result.getDataType());
     }
 
     @Test
     void returnsReconvertedTypeWhenTypesNotNull() {
-        Column column = mock(Column.class);
-        when(column.getSinkType()).thenReturn("Int64");
-        when(column.getDataType()).thenReturn((SeaTunnelDataType) 
BasicType.SHORT_TYPE);
-        when(column.getName()).thenReturn("col1");
-        FieldType result = MilvusSinkConverter.convertToFieldType(column, 
null, null, null);
+        Column column = columnWithSinkType("col1", "Int64");
+        FieldType result = MilvusSinkConverter.convertToFieldType(column, 
null, null, null, false);
 
         assertEquals(DataType.Int64, result.getDataType());
     }
+
+    @Test
+    void convertsNullableColumnToNullableMilvusField() {
+        Column column =
+                PhysicalColumn.of("nullable_col", BasicType.STRING_TYPE, 0L, 
true, null, "");
+
+        FieldType result = MilvusSinkConverter.convertToFieldType(column, 
null, null, null, true);
+
+        assertTrue(result.isNullable());
+    }
+
+    @Test
+    void keepsNotNullableColumnNotNullableWhenNullableFieldEnabled() {
+        Column column =
+                PhysicalColumn.of("nullable_col", BasicType.STRING_TYPE, 0L, 
false, null, "");
+
+        FieldType result = MilvusSinkConverter.convertToFieldType(column, 
null, null, null, true);
+
+        assertFalse(result.isNullable());
+    }
+
+    @Test
+    void keepsPrimaryKeyAndVectorFieldNotNullableWhenNullableFieldEnabled() {
+        Column primaryKeyColumn = PhysicalColumn.of("id", BasicType.LONG_TYPE, 
0L, false, null, "");
+        FieldType primaryKey =
+                MilvusSinkConverter.convertToFieldType(
+                        primaryKeyColumn,
+                        PrimaryKey.of("id", Collections.singletonList("id")),
+                        null,
+                        false,
+                        true);
+
+        Column vectorColumn =
+                PhysicalColumn.of("vector", VectorType.VECTOR_FLOAT_TYPE, 0L, 
4, false, null, "");
+        FieldType vector =
+                MilvusSinkConverter.convertToFieldType(vectorColumn, null, 
null, null, true);
+
+        assertEquals(false, primaryKey.isNullable());
+        assertEquals(false, vector.isNullable());
+    }
+
+    @Test
+    void keepsPartitionKeyNotNullableWhenNullableFieldEnabled() {
+        Column partitionKeyColumn =
+                PhysicalColumn.of("partition_col", BasicType.STRING_TYPE, 0L, 
true, null, "");
+
+        FieldType partitionKey =
+                MilvusSinkConverter.convertToFieldType(
+                        partitionKeyColumn, null, "partition_col", null, true);
+
+        assertFalse(partitionKey.isNullable());
+        assertTrue(partitionKey.isPartitionKey());
+    }
+
+    @Test
+    void throwsWhenFieldValueIsNullByDefault() {
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {null});
+
+        MilvusConnectorException exception =
+                assertThrows(
+                        MilvusConnectorException.class,
+                        () ->
+                                new MilvusSinkConverter()
+                                        .buildMilvusData(
+                                                catalogTable(),
+                                                
ReadonlyConfig.fromMap(Collections.emptyMap()),
+                                                Collections.emptyList(),
+                                                null,
+                                                row));
+
+        assertEquals("MILVUS-10", exception.getSeaTunnelErrorCode().getCode());
+    }
+
+    @Test
+    void keepsNullFieldValueInMilvusDataWhenNullableFieldEnabled() {
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {null});
+
+        assertEquals(
+                JsonNull.INSTANCE,
+                new MilvusSinkConverter()
+                        .buildMilvusData(
+                                catalogTable(),
+                                nullableFieldConfig(),
+                                Collections.emptyList(),
+                                null,
+                                row)
+                        .get("nullable_col"));
+    }
+
+    @Test
+    void throwsWhenNotNullableFieldValueIsNullAndNullableFieldEnabled() {
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {null});
+
+        MilvusConnectorException exception =
+                assertThrows(
+                        MilvusConnectorException.class,
+                        () ->
+                                new MilvusSinkConverter()
+                                        .buildMilvusData(
+                                                catalogTable(false),
+                                                nullableFieldConfig(),
+                                                Collections.emptyList(),
+                                                null,
+                                                row));
+
+        assertEquals("MILVUS-10", exception.getSeaTunnelErrorCode().getCode());
+    }
+
+    @Test
+    void throwsWhenPartitionKeyValueIsNullAndNullableFieldEnabled() {
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {null});
+
+        MilvusConnectorException exception =
+                assertThrows(
+                        MilvusConnectorException.class,
+                        () ->
+                                new MilvusSinkConverter()
+                                        .buildMilvusData(
+                                                catalogTable(),
+                                                nullablePartitionKeyConfig(),
+                                                Collections.emptyList(),
+                                                null,
+                                                row));
+
+        assertEquals("MILVUS-10", exception.getSeaTunnelErrorCode().getCode());
+    }
+
+    @Test
+    void throwsWhenDynamicFieldValueIsNullByDefault() {
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {null});
+
+        MilvusConnectorException exception =
+                assertThrows(
+                        MilvusConnectorException.class,
+                        () ->
+                                new MilvusSinkConverter()
+                                        .buildMilvusData(
+                                                dynamicCatalogTable(),
+                                                
ReadonlyConfig.fromMap(Collections.emptyMap()),
+                                                Collections.emptyList(),
+                                                "dynamic_col",
+                                                row));
+
+        assertEquals("MILVUS-10", exception.getSeaTunnelErrorCode().getCode());
+    }
+
+    private CatalogTable catalogTable() {
+        return catalogTable(true);
+    }
+
+    private CatalogTable catalogTable(boolean nullable) {
+        return CatalogTable.of(
+                TableIdentifier.of("catalog", "database", "table"),
+                TableSchema.builder()
+                        .column(
+                                PhysicalColumn.of(
+                                        "nullable_col",
+                                        BasicType.STRING_TYPE,
+                                        0L,
+                                        nullable,
+                                        null,
+                                        ""))
+                        .build(),
+                Collections.emptyMap(),
+                Collections.emptyList(),
+                "");
+    }
+
+    private CatalogTable dynamicCatalogTable() {
+        Map<String, Object> options = new HashMap<>();
+        options.put(CommonOptions.METADATA.getName(), true);
+        return CatalogTable.of(
+                TableIdentifier.of("catalog", "database", "table"),
+                TableSchema.builder()
+                        .column(
+                                new PhysicalColumn(
+                                        "dynamic_col",
+                                        BasicType.STRING_TYPE,
+                                        0L,
+                                        null,
+                                        false,
+                                        null,
+                                        "",
+                                        null,
+                                        null,
+                                        options))
+                        .build(),
+                Collections.emptyMap(),
+                Collections.emptyList(),
+                "");
+    }
+
+    private ReadonlyConfig nullableFieldConfig() {
+        Map<String, Object> options = new HashMap<>();
+        options.put(MilvusSinkOptions.ENABLE_NULLABLE_FIELD.key(), true);
+        return ReadonlyConfig.fromMap(options);
+    }
+
+    private ReadonlyConfig nullablePartitionKeyConfig() {
+        Map<String, Object> options = new HashMap<>();
+        options.put(MilvusSinkOptions.ENABLE_NULLABLE_FIELD.key(), true);
+        options.put(MilvusSinkOptions.PARTITION_KEY.key(), "nullable_col");
+        return ReadonlyConfig.fromMap(options);
+    }
+
+    private Column columnWithSinkType(String name, String sinkType) {
+        return new PhysicalColumn(
+                name,
+                BasicType.SHORT_TYPE,
+                0L,
+                null,
+                true,
+                null,
+                "",
+                sinkType,
+                null,
+                Collections.emptyMap());
+    }
 }
diff --git 
a/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverterTest.java
 
b/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverterTest.java
new file mode 100644
index 0000000000..c323400566
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-milvus/src/test/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverterTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.connectors.seatunnel.milvus.utils.source;
+
+import org.apache.seatunnel.api.table.catalog.PhysicalColumn;
+
+import org.junit.jupiter.api.Test;
+
+import io.milvus.grpc.DataType;
+import io.milvus.grpc.FieldSchema;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class MilvusSourceConverterTest {
+
+    @Test
+    void convertColumnPreservesNullable() {
+        FieldSchema fieldSchema =
+                FieldSchema.newBuilder()
+                        .setName("nullable_col")
+                        .setDataType(DataType.VarChar)
+                        .setNullable(true)
+                        .build();
+
+        PhysicalColumn column = 
MilvusSourceConverter.convertColumn(fieldSchema);
+
+        assertTrue(column.isNullable());
+    }
+}
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-milvus-e2e/src/test/java/org/apache/seatunnel/e2e/connector/v2/milvus/MilvusIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-milvus-e2e/src/test/java/org/apache/seatunnel/e2e/connector/v2/milvus/MilvusIT.java
index fe78508d7b..dd934c263d 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-milvus-e2e/src/test/java/org/apache/seatunnel/e2e/connector/v2/milvus/MilvusIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-milvus-e2e/src/test/java/org/apache/seatunnel/e2e/connector/v2/milvus/MilvusIT.java
@@ -54,9 +54,11 @@ import io.milvus.client.MilvusServiceClient;
 import io.milvus.grpc.DataType;
 import io.milvus.grpc.DescribeCollectionResponse;
 import io.milvus.grpc.DescribeIndexResponse;
+import io.milvus.grpc.FieldData;
 import io.milvus.grpc.FieldSchema;
 import io.milvus.grpc.IndexDescription;
 import io.milvus.grpc.KeyValuePair;
+import io.milvus.grpc.ListDatabasesResponse;
 import io.milvus.grpc.MutationResult;
 import io.milvus.grpc.QueryResults;
 import io.milvus.param.ConnectParam;
@@ -100,13 +102,14 @@ import java.util.stream.Stream;
 public class MilvusIT extends TestSuiteBase implements TestResource {
 
     private static final String HOST = "milvus-e2e";
-    private static final String MILVUS_IMAGE = 
"milvusdb/milvus:2.4-20240711-7e2a9d6b";
+    private static final String MILVUS_IMAGE = "milvusdb/milvus:v2.5.11";
     private static final String TOKEN = "root:Milvus";
     private MilvusContainer container;
     private MilvusServiceClient milvusClient;
     private static final String COLLECTION_NAME = "simple_example";
     private static final String COLLECTION_NAME_1 = "simple_example_1";
     private static final String COLLECTION_NAME_2 = "simple_example_2";
+    private static final String COLLECTION_NAME_NULLABLE = 
"simple_example_nullable";
     private static final String COLLECTION_NAME_WITH_PARTITIONKEY =
             "simple_example_with_partitionkey";
     private static final String COLLECTION_NAME_WITH_PARTITIONS = 
"simple_example_with_partitions";
@@ -642,6 +645,38 @@ public class MilvusIT extends TestSuiteBase implements 
TestResource {
         Assertions.assertTrue(fields.contains(TITLE_FIELD));
     }
 
+    @TestTemplate
+    public void testFakeToMilvusWithNullableField(TestContainer container)
+            throws IOException, InterruptedException {
+        String databaseName = "test_nullable";
+        catalog.createDatabase(TablePath.of(databaseName, 
COLLECTION_NAME_NULLABLE), true);
+
+        Container.ExecResult execResult = 
container.executeJob("/fake-to-milvus-nullable.conf");
+        Assertions.assertEquals(0, execResult.getExitCode());
+
+        waitCollectionReady(databaseName, COLLECTION_NAME_NULLABLE, 
VECTOR_FIELD);
+        Assertions.assertEquals(2, countCollectionEntities(databaseName, 
COLLECTION_NAME_NULLABLE));
+
+        FieldSchema titleFieldSchema =
+                describeField(databaseName, COLLECTION_NAME_NULLABLE, 
TITLE_FIELD);
+        Assertions.assertTrue(titleFieldSchema.getNullable());
+
+        FieldData nullTitleFieldData =
+                queryField(databaseName, COLLECTION_NAME_NULLABLE, 
TITLE_FIELD, ID_FIELD + " == 1");
+        Assertions.assertEquals(
+                Collections.singletonList(false), 
nullTitleFieldData.getValidDataList());
+        Assertions.assertEquals(
+                Collections.singletonList(""),
+                nullTitleFieldData.getScalars().getStringData().getDataList());
+
+        FieldData titleFieldData =
+                queryField(databaseName, COLLECTION_NAME_NULLABLE, 
TITLE_FIELD, ID_FIELD + " == 2");
+        Assertions.assertEquals(Collections.singletonList(true), 
titleFieldData.getValidDataList());
+        Assertions.assertEquals(
+                Collections.singletonList("nullable title"),
+                titleFieldData.getScalars().getStringData().getDataList());
+    }
+
     @TestTemplate
     public void testMultiFakeToMilvus(TestContainer container)
             throws IOException, InterruptedException {
@@ -885,6 +920,26 @@ public class MilvusIT extends TestSuiteBase implements 
TestResource {
 
     private void waitCollectionReady(
             String databaseName, String collectionName, String 
vectorFieldName) {
+        // assert database exist
+        Awaitility.await()
+                .atMost(60, TimeUnit.SECONDS)
+                .pollInterval(2, TimeUnit.SECONDS)
+                .until(
+                        () -> {
+                            R<ListDatabasesResponse> listDatabasesResponse =
+                                    this.milvusClient.listDatabases();
+                            Assertions.assertEquals(
+                                    R.Status.Success.getCode(),
+                                    listDatabasesResponse.getStatus(),
+                                    
Optional.ofNullable(listDatabasesResponse.getException())
+                                            .map(Exception::getMessage)
+                                            .orElse(""));
+                            return listDatabasesResponse
+                                    .getData()
+                                    .getDbNamesList()
+                                    .contains(databaseName);
+                        });
+
         // assert table exist
         Awaitility.await()
                 .atMost(60, TimeUnit.SECONDS)
@@ -955,4 +1010,36 @@ public class MilvusIT extends TestSuiteBase implements 
TestResource {
                 .getDataList()
                 .get(0);
     }
+
+    private FieldSchema describeField(
+            String databaseName, String collectionName, String fieldName) {
+        R<DescribeCollectionResponse> describeCollectionResponse =
+                this.milvusClient.describeCollection(
+                        DescribeCollectionParam.newBuilder()
+                                .withDatabaseName(databaseName)
+                                .withCollectionName(collectionName)
+                                .build());
+        Assertions.assertEquals(R.Status.Success.getCode(), 
describeCollectionResponse.getStatus());
+        return 
describeCollectionResponse.getData().getSchema().getFieldsList().stream()
+                .filter(field -> fieldName.equals(field.getName()))
+                .findFirst()
+                .orElseThrow(() -> new AssertionError("Field not found: " + 
fieldName));
+    }
+
+    private FieldData queryField(
+            String databaseName, String collectionName, String fieldName, 
String expr) {
+        R<QueryResults> queryResults =
+                milvusClient.query(
+                        QueryParam.newBuilder()
+                                .withDatabaseName(databaseName)
+                                .withCollectionName(collectionName)
+                                .withExpr(expr)
+                                
.withOutFields(Collections.singletonList(fieldName))
+                                .build());
+        Assertions.assertEquals(R.Status.Success.getCode(), 
queryResults.getStatus());
+        return queryResults.getData().getFieldsDataList().stream()
+                .filter(fieldData -> 
fieldName.equals(fieldData.getFieldName()))
+                .findFirst()
+                .orElseThrow(() -> new AssertionError("Field data not found: " 
+ fieldName));
+    }
 }
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-milvus-e2e/src/test/resources/fake-to-milvus-nullable.conf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-milvus-e2e/src/test/resources/fake-to-milvus-nullable.conf
new file mode 100644
index 0000000000..5681ee930f
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-milvus-e2e/src/test/resources/fake-to-milvus-nullable.conf
@@ -0,0 +1,73 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+env {
+  parallelism = 1
+  job.mode = "BATCH"
+}
+
+source {
+  FakeSource {
+      schema = {
+           table = "simple_example_nullable"
+           columns = [
+           {
+              name = book_id
+              type = bigint
+              nullable = false
+              comment = "primary key id"
+           },
+           {
+              name = book_intro
+              type = float_vector
+              columnScale = 4
+              nullable = false
+              comment = "vector"
+           },
+           {
+              name = book_title
+              type = string
+              nullable = true
+              comment = "nullable title"
+           }
+       ]
+        primaryKey {
+            name = book_id
+            columnNames = [book_id]
+        }
+      }
+      rows = [
+        {
+          kind = INSERT
+          fields = [1, [0.1, 0.2, 0.3, 0.4], null]
+        },
+        {
+          kind = INSERT
+          fields = [2, [0.2, 0.3, 0.4, 0.5], "nullable title"]
+        }
+      ]
+  }
+}
+
+sink {
+   Milvus {
+     url = "http://milvus-e2e:19530";
+     token = "root:Milvus"
+     database = "test_nullable"
+     enable_nullable_field = true
+   }
+}
diff --git 
a/seatunnel-formats/seatunnel-format-json/src/main/java/org/apache/seatunnel/format/json/JsonToRowConverters.java
 
b/seatunnel-formats/seatunnel-format-json/src/main/java/org/apache/seatunnel/format/json/JsonToRowConverters.java
index d5f46f5202..c4aaf5e436 100644
--- 
a/seatunnel-formats/seatunnel-format-json/src/main/java/org/apache/seatunnel/format/json/JsonToRowConverters.java
+++ 
b/seatunnel-formats/seatunnel-format-json/src/main/java/org/apache/seatunnel/format/json/JsonToRowConverters.java
@@ -32,6 +32,7 @@ import 
org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated;
 import org.apache.seatunnel.common.utils.DateTimeUtils;
 import org.apache.seatunnel.common.utils.DateUtils;
 import org.apache.seatunnel.common.utils.JsonUtils;
+import org.apache.seatunnel.common.utils.VectorUtils;
 import org.apache.seatunnel.format.json.exception.SeaTunnelJsonFormatException;
 
 import java.io.IOException;
@@ -198,6 +199,13 @@ public class JsonToRowConverters implements Serializable {
                         return convertToBigDecimal(jsonNode);
                     }
                 };
+            case FLOAT_VECTOR:
+                return new JsonToObjectConverter() {
+                    @Override
+                    public Object convert(JsonNode jsonNode, String fieldName) 
{
+                        return convertToFloatVector(jsonNode, fieldName);
+                    }
+                };
             case ARRAY:
                 return createArrayConverter((ArrayType<?, ?>) type);
             case MAP:
@@ -342,6 +350,27 @@ public class JsonToRowConverters implements Serializable {
         return bigDecimal;
     }
 
+    private Object convertToFloatVector(JsonNode jsonNode, String fieldName) {
+        if (!jsonNode.isArray()) {
+            throw new SeaTunnelJsonFormatException(
+                    CommonErrorCodeDeprecated.UNSUPPORTED_DATA_TYPE,
+                    String.format("Field '%s' expects an array for 
FLOAT_VECTOR.", fieldName));
+        }
+        Float[] values = new Float[jsonNode.size()];
+        for (int i = 0; i < jsonNode.size(); i++) {
+            JsonNode element = jsonNode.get(i);
+            if (element == null || element.isNull() || !element.isNumber()) {
+                throw new SeaTunnelJsonFormatException(
+                        CommonErrorCodeDeprecated.UNSUPPORTED_DATA_TYPE,
+                        String.format(
+                                "Field '%s' expects numeric values for 
FLOAT_VECTOR, but element at index %d is '%s'.",
+                                fieldName, i, element));
+            }
+            values[i] = convertToFloat(element);
+        }
+        return VectorUtils.toByteBuffer(values);
+    }
+
     public JsonToObjectConverter createRowConverter(SeaTunnelRowType rowType) {
         final JsonToObjectConverter[] fieldConverters =
                 Arrays.stream(rowType.getFieldTypes())
diff --git 
a/seatunnel-formats/seatunnel-format-json/src/test/java/org/apache/seatunnel/format/json/JsonRowDataSerDeSchemaTest.java
 
b/seatunnel-formats/seatunnel-format-json/src/test/java/org/apache/seatunnel/format/json/JsonRowDataSerDeSchemaTest.java
index 4194df4adf..66d038e105 100644
--- 
a/seatunnel-formats/seatunnel-format-json/src/test/java/org/apache/seatunnel/format/json/JsonRowDataSerDeSchemaTest.java
+++ 
b/seatunnel-formats/seatunnel-format-json/src/test/java/org/apache/seatunnel/format/json/JsonRowDataSerDeSchemaTest.java
@@ -35,6 +35,7 @@ import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
 import org.apache.seatunnel.common.exception.CommonError;
 import org.apache.seatunnel.common.exception.SeaTunnelRuntimeException;
 import org.apache.seatunnel.common.utils.JsonUtils;
+import org.apache.seatunnel.common.utils.VectorUtils;
 import org.apache.seatunnel.format.json.exception.SeaTunnelJsonFormatException;
 
 import org.junit.jupiter.api.Assertions;
@@ -42,6 +43,7 @@ import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.sql.Timestamp;
 import java.time.LocalDate;
@@ -63,9 +65,12 @@ import static 
org.apache.seatunnel.api.table.type.BasicType.INT_TYPE;
 import static org.apache.seatunnel.api.table.type.BasicType.LONG_TYPE;
 import static org.apache.seatunnel.api.table.type.BasicType.SHORT_TYPE;
 import static org.apache.seatunnel.api.table.type.BasicType.STRING_TYPE;
+import static org.apache.seatunnel.api.table.type.VectorType.VECTOR_FLOAT_TYPE;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class JsonRowDataSerDeSchemaTest {
 
@@ -577,6 +582,36 @@ public class JsonRowDataSerDeSchemaTest {
         assertEquals(expect, keyMap.keySet().iterator().next());
     }
 
+    @Test
+    public void testFloatVectorConverter() throws JsonProcessingException {
+        JsonToRowConverters converters = new JsonToRowConverters(true, false);
+        JsonToRowConverters.JsonToObjectConverter converter =
+                converters.createConverter(VECTOR_FLOAT_TYPE);
+
+        ByteBuffer result =
+                (ByteBuffer)
+                        converter.convert(
+                                JsonUtils.stringToJsonNode("[0.1, 0.2, 0.3, 
0.4]"), "vector");
+
+        assertArrayEquals(new Float[] {0.1f, 0.2f, 0.3f, 0.4f}, 
VectorUtils.toFloatArray(result));
+    }
+
+    @Test
+    public void testFloatVectorConverterRejectsInvalidElement() throws 
JsonProcessingException {
+        JsonToRowConverters converters = new JsonToRowConverters(true, false);
+        JsonToRowConverters.JsonToObjectConverter converter =
+                converters.createConverter(VECTOR_FLOAT_TYPE);
+
+        SeaTunnelJsonFormatException exception =
+                assertThrows(
+                        SeaTunnelJsonFormatException.class,
+                        () ->
+                                converter.convert(
+                                        JsonUtils.stringToJsonNode("[0.1, 
null]"), "vector"));
+
+        assertTrue(exception.getMessage().contains("element at index 1"));
+    }
+
     @Test
     public void testParseUnsupportedDateTimeFormat() throws IOException {
         SeaTunnelRowType rowType =

Reply via email to