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-12839-2be65da8caceb528790e1ff794b4b842e55b69eb
in repository https://gitbox.apache.org/repos/asf/seatunnel.git

commit 68c686438f31fcbaa376081af6a4536caa227f12
Author: Goutam Adwant <[email protected]>
AuthorDate: Sun Oct 18 06:02:54 2026 +0000

    [Fix][Connector-V2] Add opt-in collision-safe Easysearch and Typesense IDs 
(#12839)
---
 docs/en/connectors/sink/Easysearch.md              |  7 ++
 docs/en/connectors/sink/Typesense.md               |  7 ++
 docs/zh/connectors/sink/Easysearch.md              |  7 ++
 docs/zh/connectors/sink/Typesense.md               |  7 ++
 .../easysearch/config/EasysearchSinkOptions.java   |  7 ++
 .../seatunnel/easysearch/config/KeyEncoding.java}  | 26 ++-----
 .../seatunnel/easysearch/dto/IndexInfo.java        |  3 +
 .../serialize/EasysearchRowSerializer.java         |  5 +-
 .../easysearch/serialize/KeyExtractor.java         | 25 ++++--
 .../easysearch/sink/EasysearchSinkFactory.java     |  1 +
 .../easysearch/serialize/KeyExtractorTest.java     | 89 ++++++++++++++++++++++
 .../KeyEncoding.java}                              | 26 ++-----
 .../typesense/config/TypesenseSinkOptions.java     |  7 ++
 .../seatunnel/typesense/dto/CollectionInfo.java    |  3 +
 .../typesense/serialize/KeyExtractor.java          | 25 ++++--
 .../serialize/sink/TypesenseRowSerializer.java     | 10 +--
 .../typesense/sink/TypesenseSinkFactory.java       |  1 +
 .../serializer/TypesenseRowSerializerTest.java     | 50 ++++++++++++
 .../e2e/connector/easysearch/EasysearchIT.java     | 21 +++++
 .../easysearch_composite_key_encoding.conf         | 50 ++++++++++++
 .../e2e/connector/typesense/TypesenseIT.java       |  9 +++
 .../fake_to_typesense_composite_key_encoding.conf  | 47 ++++++++++++
 22 files changed, 375 insertions(+), 58 deletions(-)

diff --git a/docs/en/connectors/sink/Easysearch.md 
b/docs/en/connectors/sink/Easysearch.md
index 671c95838e..15071472df 100644
--- a/docs/en/connectors/sink/Easysearch.md
+++ b/docs/en/connectors/sink/Easysearch.md
@@ -52,6 +52,7 @@ Engine Supported
 | index                  | string  | yes      | -             |
 | primary_keys           | list    | no       |               |
 | key_delimiter          | string  | no       | `_`           |
+| key_encoding           | enum    | no       | LEGACY        |
 | username               | string  | no       |               |
 | password               | string  | no       |               |
 | max_retry_count        | int     | no       | 3             |
@@ -82,6 +83,12 @@ Primary key fields used to generate the document `_id`. 
Configure this option wh
 
 Delimiter for composite keys ("_" by default), e.g., "$" would result in 
document `_id` "KEY1$KEY2$KEY3".
 
+### key_encoding [enum]
+
+`LEGACY` (default) keeps existing document IDs. With multiple `primary_keys`, 
values containing `key_delimiter` can produce the same ID. Set 
`LENGTH_PREFIXED` to encode each formatted value as `<length>:<value>`; for 
example, `["1_1", "2"]` becomes `3:1_11:2` and `["1", "1_2"]` becomes 
`1:13:1_2`. This mode ignores `key_delimiter` and leaves single-key IDs 
unchanged.
+
+Changing this option for an existing composite-key index changes its document 
IDs. Reindex or migrate existing documents before switching; otherwise 
subsequent writes can create duplicates instead of updating the old IDs.
+
 ### username [string]
 
 security username
diff --git a/docs/en/connectors/sink/Typesense.md 
b/docs/en/connectors/sink/Typesense.md
index dcc538dafb..527efefd62 100644
--- a/docs/en/connectors/sink/Typesense.md
+++ b/docs/en/connectors/sink/Typesense.md
@@ -33,6 +33,7 @@ or more primary key fields.
 | data_save_mode           | string | Yes      | APPEND_DATA                  
| How to handle existing documents before writing.                              
                       |
 | primary_keys             | array  | No       | -                            
| Source fields used to build the Typesense document `id`.                      
                       |
 | key_delimiter            | string | No       | `_`                          
| Delimiter used when `primary_keys` contains more than one field.              
                       |
+| key_encoding             | enum   | No       | LEGACY                       
| Optional collision-safe encoding for composite document IDs.                  
                       |
 | api_key                  | string | Yes      | -                            
| Typesense API key.                                                            
                       |
 | max_retry_count          | int    | No       | 3                            
| Maximum retry count for one bulk request.                                     
                       |
 | max_batch_size           | int    | No       | 10                           
| Maximum number of documents sent in one bulk request.                         
                       |
@@ -56,6 +57,12 @@ the connector joins their values with `key_delimiter`. 
Without `primary_keys`, T
 
 Sets the delimiter for composite keys (default is `_`).
 
+### key_encoding [enum]
+
+`LEGACY` (default) preserves existing IDs, but composite keys can collide when 
a value contains `key_delimiter`. `LENGTH_PREFIXED` encodes each formatted 
value as `<length>:<value>`: `["1_1", "2"]` becomes `3:1_11:2`, while `["1", 
"1_2"]` becomes `1:13:1_2`. It ignores `key_delimiter` and does not change 
single-key IDs.
+
+Switching an existing composite-key collection changes document IDs. Migrate 
or recreate existing documents first; otherwise later writes can create 
duplicates instead of updating them.
+
 ### api_key [string]
 
 The `api_key` for secure access to Typesense. Treat this value as a secret and 
prefer passing it via a job secret or environment variable when running on 
shared infrastructure.
diff --git a/docs/zh/connectors/sink/Easysearch.md 
b/docs/zh/connectors/sink/Easysearch.md
index 53f77d6531..109217a7c1 100644
--- a/docs/zh/connectors/sink/Easysearch.md
+++ b/docs/zh/connectors/sink/Easysearch.md
@@ -51,6 +51,7 @@ import ChangeLog from '../changelog/connector-easysearch.md';
 | index                  | string  | 是  | -             |
 | primary_keys           | list    | 否       | -             |
 | key_delimiter          | string  | 否       | `_`           |
+| key_encoding           | enum    | 否       | LEGACY        |
 | username               | string  | 否       | -             |
 | password               | string  | 否       | -             |
 | max_retry_count        | int     | 否       | 3             |
@@ -81,6 +82,12 @@ import ChangeLog from '../changelog/connector-easysearch.md';
 
 复合键的分隔符 (默认为"_" ), 例如, "$" 将导致文档 `_id` "KEY1$KEY2$KEY3".
 
+### key_encoding [enum]
+
+`LEGACY`(默认)保持现有文档 ID 不变。配置多个 `primary_keys` 时,字段值包含 `key_delimiter` 
可能导致不同记录生成相同 ID。设置为 `LENGTH_PREFIXED` 后,每个格式化后的字段值按 `<长度>:<值>` 编码,例如 `["1_1", 
"2"]` 生成 `3:1_11:2`,`["1", "1_2"]` 生成 `1:13:1_2`。该模式忽略 `key_delimiter`,单字段主键的 
ID 不变。
+
+对已有复合键索引切换此选项会改变文档 ID。切换前应重建索引或迁移已有文档,否则后续写入可能创建重复文档,而不是更新原文档。
+
 ### username [string]
 
 安全用户名
diff --git a/docs/zh/connectors/sink/Typesense.md 
b/docs/zh/connectors/sink/Typesense.md
index c9217c8dd3..d5da897b62 100644
--- a/docs/zh/connectors/sink/Typesense.md
+++ b/docs/zh/connectors/sink/Typesense.md
@@ -32,6 +32,7 @@ import ChangeLog from '../changelog/connector-typesense.md';
 | data_save_mode           | string | 是    | APPEND_DATA                  | 
写入前如何处理目标 collection 中已有文档。                      |
 | primary_keys             | array  | 否    | -                            | 
用于生成 Typesense 文档 `id` 的源字段。                     |
 | key_delimiter            | string | 否    | `_`                          | 
`primary_keys` 配置多个字段时使用的拼接分隔符。                  |
+| key_encoding             | enum   | 否    | LEGACY                       | 
可选的复合文档 ID 防冲突编码方式。                             |
 | api_key                  | string | 是    | -                            | 
Typesense API Key。                                 |
 | max_retry_count          | int    | 否    | 3                            | 
单个批量请求的最大重试次数。                                  |
 | max_batch_size           | int    | 否    | 10                           | 
单个批量请求最多写入的文档数量。                                |
@@ -54,6 +55,12 @@ Typesense 的访问地址,格式为 `host:port`,例如:`["typesense-01:810
 
 设定复合键的分隔符(默认为 `_`)。
 
+### key_encoding [enum]
+
+`LEGACY`(默认)保持现有 ID 不变,但字段值包含 `key_delimiter` 时复合键可能发生 ID 冲突。`LENGTH_PREFIXED` 
将每个格式化后的字段值按 `<长度>:<值>` 编码:`["1_1", "2"]` 生成 `3:1_11:2`,`["1", "1_2"]` 生成 
`1:13:1_2`。该模式忽略 `key_delimiter`,单字段主键的 ID 不变。
+
+对已有复合键 collection 切换此选项会改变文档 ID。切换前应迁移或重新创建已有文档,否则后续写入可能创建重复文档,而不是更新原文档。
+
 ### api_key [string]
 
 Typesense 安全认证的 `api_key`。请把它当作敏感凭据处理;在共享环境运行时,建议通过作业密钥或环境变量注入。
diff --git 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/config/EasysearchSinkOptions.java
 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/config/EasysearchSinkOptions.java
index a7d0535b2f..fc504e1930 100644
--- 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/config/EasysearchSinkOptions.java
+++ 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/config/EasysearchSinkOptions.java
@@ -44,6 +44,13 @@ public class EasysearchSinkOptions extends 
EasysearchSinkCommonOptions {
                     .withDescription(
                             "Delimiter for composite keys (\"_\" by default), 
e.g., \"$\" would result in document `_id` \"KEY1$KEY2$KEY3\".");
 
+    public static final Option<KeyEncoding> KEY_ENCODING =
+            Options.key("key_encoding")
+                    .enumType(KeyEncoding.class)
+                    .defaultValue(KeyEncoding.LEGACY)
+                    .withDescription(
+                            "LEGACY preserves existing document IDs; 
LENGTH_PREFIXED avoids composite-key delimiter collisions.");
+
     public static final Option<Integer> MAX_BATCH_SIZE =
             Options.key("max_batch_size")
                     .intType()
diff --git 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/config/KeyEncoding.java
similarity index 51%
copy from 
seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
copy to 
seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/config/KeyEncoding.java
index e2ab88c832..2654ae752f 100644
--- 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
+++ 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/config/KeyEncoding.java
@@ -15,26 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.seatunnel.connectors.seatunnel.typesense.dto;
+package org.apache.seatunnel.connectors.seatunnel.easysearch.config;
 
-import org.apache.seatunnel.api.configuration.ReadonlyConfig;
-import 
org.apache.seatunnel.connectors.seatunnel.typesense.config.TypesenseSinkOptions;
-
-import lombok.Data;
-
-@Data
-public class CollectionInfo {
-
-    private String collection;
-    private String type;
-    private String[] primaryKeys;
-    private String keyDelimiter;
-
-    public CollectionInfo(String collection, ReadonlyConfig config) {
-        this.collection = collection;
-        if (config.getOptional(TypesenseSinkOptions.PRIMARY_KEYS).isPresent()) 
{
-            primaryKeys = 
config.get(TypesenseSinkOptions.PRIMARY_KEYS).toArray(new String[0]);
-        }
-        keyDelimiter = config.get(TypesenseSinkOptions.KEY_DELIMITER);
-    }
+/** Encoding of composite document IDs. Existing indexes use {@link #LEGACY}. 
*/
+public enum KeyEncoding {
+    LEGACY,
+    LENGTH_PREFIXED
 }
diff --git 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/dto/IndexInfo.java
 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/dto/IndexInfo.java
index 8da4a0854d..961b876f64 100644
--- 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/dto/IndexInfo.java
+++ 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/dto/IndexInfo.java
@@ -20,6 +20,7 @@ package 
org.apache.seatunnel.connectors.seatunnel.easysearch.dto;
 import org.apache.seatunnel.api.configuration.ReadonlyConfig;
 import 
org.apache.seatunnel.connectors.seatunnel.easysearch.config.EasysearchSinkCommonOptions;
 import 
org.apache.seatunnel.connectors.seatunnel.easysearch.config.EasysearchSinkOptions;
+import org.apache.seatunnel.connectors.seatunnel.easysearch.config.KeyEncoding;
 
 import lombok.Data;
 
@@ -30,6 +31,7 @@ public class IndexInfo {
     private String index;
     private String[] primaryKeys;
     private String keyDelimiter;
+    private KeyEncoding keyEncoding;
 
     public IndexInfo(ReadonlyConfig pluginConfig) {
         index = pluginConfig.get(EasysearchSinkCommonOptions.INDEX);
@@ -38,5 +40,6 @@ public class IndexInfo {
                     
pluginConfig.get(EasysearchSinkOptions.PRIMARY_KEYS).toArray(new String[0]);
         }
         keyDelimiter = pluginConfig.get(EasysearchSinkOptions.KEY_DELIMITER);
+        keyEncoding = pluginConfig.get(EasysearchSinkOptions.KEY_ENCODING);
     }
 }
diff --git 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/EasysearchRowSerializer.java
 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/EasysearchRowSerializer.java
index e57ad1fd51..c106fe531e 100644
--- 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/EasysearchRowSerializer.java
+++ 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/EasysearchRowSerializer.java
@@ -51,7 +51,10 @@ public class EasysearchRowSerializer implements 
SeaTunnelRowSerializer {
         this.seaTunnelRowType = seaTunnelRowType;
         this.keyExtractor =
                 KeyExtractor.createKeyExtractor(
-                        seaTunnelRowType, indexInfo.getPrimaryKeys(), 
indexInfo.getKeyDelimiter());
+                        seaTunnelRowType,
+                        indexInfo.getPrimaryKeys(),
+                        indexInfo.getKeyDelimiter(),
+                        indexInfo.getKeyEncoding());
     }
 
     @Override
diff --git 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/KeyExtractor.java
 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/KeyExtractor.java
index c732d4288b..bf8ab7f396 100644
--- 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/KeyExtractor.java
+++ 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/KeyExtractor.java
@@ -20,6 +20,7 @@ package 
org.apache.seatunnel.connectors.seatunnel.easysearch.serialize;
 import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
 import org.apache.seatunnel.api.table.type.SeaTunnelRow;
 import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
+import org.apache.seatunnel.connectors.seatunnel.easysearch.config.KeyEncoding;
 import 
org.apache.seatunnel.connectors.seatunnel.easysearch.exception.EasysearchConnectorException;
 
 import lombok.AllArgsConstructor;
@@ -38,9 +39,18 @@ import static 
org.apache.seatunnel.connectors.seatunnel.easysearch.exception.Eas
 public class KeyExtractor implements Function<SeaTunnelRow, String>, 
Serializable {
     private final FieldFormatter[] fieldFormatters;
     private final String keyDelimiter;
+    private final KeyEncoding keyEncoding;
 
     public static Function<SeaTunnelRow, String> createKeyExtractor(
             SeaTunnelRowType rowType, String[] primaryKeys, String 
keyDelimiter) {
+        return createKeyExtractor(rowType, primaryKeys, keyDelimiter, 
KeyEncoding.LEGACY);
+    }
+
+    public static Function<SeaTunnelRow, String> createKeyExtractor(
+            SeaTunnelRowType rowType,
+            String[] primaryKeys,
+            String keyDelimiter,
+            KeyEncoding keyEncoding) {
         if (primaryKeys == null) {
             return row -> null;
         }
@@ -52,7 +62,8 @@ public class KeyExtractor implements Function<SeaTunnelRow, 
String>, Serializabl
             FieldFormatter fieldFormatter = createFieldFormatter(fieldIndex, 
fieldType);
             fieldFormatters.add(fieldFormatter);
         }
-        return new KeyExtractor(fieldFormatters.toArray(new 
FieldFormatter[0]), keyDelimiter);
+        return new KeyExtractor(
+                fieldFormatters.toArray(new FieldFormatter[0]), keyDelimiter, 
keyEncoding);
     }
 
     private static FieldFormatter createFieldFormatter(
@@ -83,11 +94,15 @@ public class KeyExtractor implements Function<SeaTunnelRow, 
String>, Serializabl
     public String apply(SeaTunnelRow row) {
         StringBuilder builder = new StringBuilder();
         for (int i = 0; i < fieldFormatters.length; i++) {
-            if (i > 0) {
-                builder.append(keyDelimiter);
-            }
             String value = fieldFormatters[i].format(row);
-            builder.append(value);
+            if (keyEncoding == KeyEncoding.LENGTH_PREFIXED && 
fieldFormatters.length > 1) {
+                builder.append(value.length()).append(':').append(value);
+            } else {
+                if (i > 0) {
+                    builder.append(keyDelimiter);
+                }
+                builder.append(value);
+            }
         }
         return builder.toString();
     }
diff --git 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/sink/EasysearchSinkFactory.java
 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/sink/EasysearchSinkFactory.java
index ec38d23166..23fd6dfacf 100644
--- 
a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/sink/EasysearchSinkFactory.java
+++ 
b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/sink/EasysearchSinkFactory.java
@@ -54,6 +54,7 @@ public class EasysearchSinkFactory implements 
TableSinkFactory {
                         EasysearchSinkOptions.PASSWORD,
                         EasysearchSinkOptions.PRIMARY_KEYS,
                         EasysearchSinkOptions.KEY_DELIMITER,
+                        EasysearchSinkOptions.KEY_ENCODING,
                         EasysearchSinkOptions.TLS_VERIFY_CERTIFICATE,
                         EasysearchSinkOptions.TLS_VERIFY_HOSTNAME,
                         EasysearchSinkOptions.TLS_KEY_STORE_PATH,
diff --git 
a/seatunnel-connectors-v2/connector-easysearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/KeyExtractorTest.java
 
b/seatunnel-connectors-v2/connector-easysearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/KeyExtractorTest.java
new file mode 100644
index 0000000000..34875cbbc4
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-easysearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/easysearch/serialize/KeyExtractorTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.easysearch.serialize;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.table.type.BasicType;
+import org.apache.seatunnel.api.table.type.RowKind;
+import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
+import org.apache.seatunnel.api.table.type.SeaTunnelRow;
+import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
+import 
org.apache.seatunnel.connectors.seatunnel.easysearch.config.EasysearchSinkOptions;
+import org.apache.seatunnel.connectors.seatunnel.easysearch.config.KeyEncoding;
+import org.apache.seatunnel.connectors.seatunnel.easysearch.dto.IndexInfo;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+class KeyExtractorTest {
+    private static final SeaTunnelRowType ROW_TYPE =
+            new SeaTunnelRowType(
+                    new String[] {"first", "second"},
+                    new SeaTunnelDataType<?>[] {BasicType.STRING_TYPE, 
BasicType.STRING_TYPE});
+
+    @Test
+    void lengthPrefixedIdsSeparateDelimiterCollisions() {
+        Function<SeaTunnelRow, String> legacy =
+                KeyExtractor.createKeyExtractor(ROW_TYPE, new String[] 
{"first", "second"}, "_");
+        Function<SeaTunnelRow, String> safe =
+                KeyExtractor.createKeyExtractor(
+                        ROW_TYPE,
+                        new String[] {"first", "second"},
+                        "_",
+                        KeyEncoding.LENGTH_PREFIXED);
+        SeaTunnelRow left = new SeaTunnelRow(new Object[] {"1_1", "2"});
+        SeaTunnelRow right = new SeaTunnelRow(new Object[] {"1", "1_2"});
+
+        Assertions.assertEquals("1_1_2", legacy.apply(left));
+        Assertions.assertEquals(legacy.apply(left), legacy.apply(right));
+        Assertions.assertEquals("3:1_11:2", safe.apply(left));
+        Assertions.assertEquals("1:13:1_2", safe.apply(right));
+    }
+
+    @Test
+    void oneKeyKeepsItsExistingIdInBothModes() {
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {"1_1", "2"});
+        Assertions.assertEquals(
+                "1_1",
+                KeyExtractor.createKeyExtractor(
+                                ROW_TYPE, new String[] {"first"}, "_", 
KeyEncoding.LENGTH_PREFIXED)
+                        .apply(row));
+    }
+
+    @Test
+    void serializerUsesTheSameSafeIdForUpsertAndDelete() {
+        Map<String, Object> options = new HashMap<>();
+        options.put(EasysearchSinkOptions.INDEX.key(), "id_probe");
+        options.put(EasysearchSinkOptions.PRIMARY_KEYS.key(), 
Arrays.asList("first", "second"));
+        options.put(EasysearchSinkOptions.KEY_ENCODING.key(), 
"LENGTH_PREFIXED");
+        EasysearchRowSerializer serializer =
+                new EasysearchRowSerializer(
+                        new IndexInfo(ReadonlyConfig.fromMap(options)), 
ROW_TYPE);
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {"1_1", "2"});
+
+        row.setRowKind(RowKind.INSERT);
+        
Assertions.assertTrue(serializer.serializeRow(row).contains("3:1_11:2"));
+        row.setRowKind(RowKind.DELETE);
+        
Assertions.assertTrue(serializer.serializeRow(row).contains("3:1_11:2"));
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/config/KeyEncoding.java
similarity index 51%
copy from 
seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
copy to 
seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/config/KeyEncoding.java
index e2ab88c832..ee6d067c4f 100644
--- 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
+++ 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/config/KeyEncoding.java
@@ -15,26 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.seatunnel.connectors.seatunnel.typesense.dto;
+package org.apache.seatunnel.connectors.seatunnel.typesense.config;
 
-import org.apache.seatunnel.api.configuration.ReadonlyConfig;
-import 
org.apache.seatunnel.connectors.seatunnel.typesense.config.TypesenseSinkOptions;
-
-import lombok.Data;
-
-@Data
-public class CollectionInfo {
-
-    private String collection;
-    private String type;
-    private String[] primaryKeys;
-    private String keyDelimiter;
-
-    public CollectionInfo(String collection, ReadonlyConfig config) {
-        this.collection = collection;
-        if (config.getOptional(TypesenseSinkOptions.PRIMARY_KEYS).isPresent()) 
{
-            primaryKeys = 
config.get(TypesenseSinkOptions.PRIMARY_KEYS).toArray(new String[0]);
-        }
-        keyDelimiter = config.get(TypesenseSinkOptions.KEY_DELIMITER);
-    }
+/** Encoding of composite document IDs. Existing indexes use {@link #LEGACY}. 
*/
+public enum KeyEncoding {
+    LEGACY,
+    LENGTH_PREFIXED
 }
diff --git 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/config/TypesenseSinkOptions.java
 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/config/TypesenseSinkOptions.java
index 7b87fbfb82..8e9d141f6d 100644
--- 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/config/TypesenseSinkOptions.java
+++ 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/config/TypesenseSinkOptions.java
@@ -44,6 +44,13 @@ public class TypesenseSinkOptions extends 
TypesenseBaseOptions {
                     .withDescription(
                             "Delimiter for composite keys (\"_\" by default), 
e.g., \"$\" would result in document `id` \"KEY1$KEY2$KEY3\".");
 
+    public static final Option<KeyEncoding> KEY_ENCODING =
+            Options.key("key_encoding")
+                    .enumType(KeyEncoding.class)
+                    .defaultValue(KeyEncoding.LEGACY)
+                    .withDescription(
+                            "LEGACY preserves existing document IDs; 
LENGTH_PREFIXED avoids composite-key delimiter collisions.");
+
     public static final Option<Integer> MAX_BATCH_SIZE =
             Options.key("max_batch_size")
                     .intType()
diff --git 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
index e2ab88c832..803a1a7530 100644
--- 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
+++ 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/dto/CollectionInfo.java
@@ -18,6 +18,7 @@
 package org.apache.seatunnel.connectors.seatunnel.typesense.dto;
 
 import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.connectors.seatunnel.typesense.config.KeyEncoding;
 import 
org.apache.seatunnel.connectors.seatunnel.typesense.config.TypesenseSinkOptions;
 
 import lombok.Data;
@@ -29,6 +30,7 @@ public class CollectionInfo {
     private String type;
     private String[] primaryKeys;
     private String keyDelimiter;
+    private KeyEncoding keyEncoding;
 
     public CollectionInfo(String collection, ReadonlyConfig config) {
         this.collection = collection;
@@ -36,5 +38,6 @@ public class CollectionInfo {
             primaryKeys = 
config.get(TypesenseSinkOptions.PRIMARY_KEYS).toArray(new String[0]);
         }
         keyDelimiter = config.get(TypesenseSinkOptions.KEY_DELIMITER);
+        keyEncoding = config.get(TypesenseSinkOptions.KEY_ENCODING);
     }
 }
diff --git 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/serialize/KeyExtractor.java
 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/serialize/KeyExtractor.java
index 5dc56f0bb7..a8cbce8a93 100644
--- 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/serialize/KeyExtractor.java
+++ 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/serialize/KeyExtractor.java
@@ -21,6 +21,7 @@ import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
 import org.apache.seatunnel.api.table.type.SeaTunnelRow;
 import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
 import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated;
+import org.apache.seatunnel.connectors.seatunnel.typesense.config.KeyEncoding;
 import 
org.apache.seatunnel.connectors.seatunnel.typesense.exception.TypesenseConnectorException;
 
 import lombok.AllArgsConstructor;
@@ -37,22 +38,35 @@ import java.util.function.Function;
 public class KeyExtractor implements Function<SeaTunnelRow, String>, 
Serializable {
     private final FieldFormatter[] fieldFormatters;
     private final String keyDelimiter;
+    private final KeyEncoding keyEncoding;
 
     @Override
     public String apply(SeaTunnelRow row) {
         StringBuilder builder = new StringBuilder();
         for (int i = 0; i < fieldFormatters.length; i++) {
-            if (i > 0) {
-                builder.append(keyDelimiter);
-            }
             String value = fieldFormatters[i].format(row);
-            builder.append(value);
+            if (keyEncoding == KeyEncoding.LENGTH_PREFIXED && 
fieldFormatters.length > 1) {
+                builder.append(value.length()).append(':').append(value);
+            } else {
+                if (i > 0) {
+                    builder.append(keyDelimiter);
+                }
+                builder.append(value);
+            }
         }
         return builder.toString();
     }
 
     public static Function<SeaTunnelRow, String> createKeyExtractor(
             SeaTunnelRowType rowType, String[] primaryKeys, String 
keyDelimiter) {
+        return createKeyExtractor(rowType, primaryKeys, keyDelimiter, 
KeyEncoding.LEGACY);
+    }
+
+    public static Function<SeaTunnelRow, String> createKeyExtractor(
+            SeaTunnelRowType rowType,
+            String[] primaryKeys,
+            String keyDelimiter,
+            KeyEncoding keyEncoding) {
         if (primaryKeys == null) {
             return row -> null;
         }
@@ -64,7 +78,8 @@ public class KeyExtractor implements Function<SeaTunnelRow, 
String>, Serializabl
             FieldFormatter fieldFormatter = createFieldFormatter(fieldIndex, 
fieldType);
             fieldFormatters.add(fieldFormatter);
         }
-        return new KeyExtractor(fieldFormatters.toArray(new 
FieldFormatter[0]), keyDelimiter);
+        return new KeyExtractor(
+                fieldFormatters.toArray(new FieldFormatter[0]), keyDelimiter, 
keyEncoding);
     }
 
     private static FieldFormatter createFieldFormatter(
diff --git 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/serialize/sink/TypesenseRowSerializer.java
 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/serialize/sink/TypesenseRowSerializer.java
index 67e0ae17f8..6ea7e48e2f 100644
--- 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/serialize/sink/TypesenseRowSerializer.java
+++ 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/serialize/sink/TypesenseRowSerializer.java
@@ -48,7 +48,8 @@ public class TypesenseRowSerializer implements 
SeaTunnelRowSerializer {
                 KeyExtractor.createKeyExtractor(
                         seaTunnelRowType,
                         collectionInfo.getPrimaryKeys(),
-                        collectionInfo.getKeyDelimiter());
+                        collectionInfo.getKeyDelimiter(),
+                        collectionInfo.getKeyEncoding());
     }
 
     @Override
@@ -70,12 +71,11 @@ public class TypesenseRowSerializer implements 
SeaTunnelRowSerializer {
     @Override
     public String serializeRowForDelete(SeaTunnelRow row) {
         String key = keyExtractor.apply(row);
-        Map<String, Object> document = toDocumentMap(row, seaTunnelRowType);
-        String id = document.get("id").toString();
         if (StringUtils.isNotBlank(key)) {
-            id = key;
+            return key;
         }
-        return id;
+        Map<String, Object> document = toDocumentMap(row, seaTunnelRowType);
+        return document.get("id").toString();
     }
 
     private Map<String, Object> toDocumentMap(SeaTunnelRow row, 
SeaTunnelRowType rowType) {
diff --git 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/sink/TypesenseSinkFactory.java
 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/sink/TypesenseSinkFactory.java
index ecd78ffa4e..cf08d17da0 100644
--- 
a/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/sink/TypesenseSinkFactory.java
+++ 
b/seatunnel-connectors-v2/connector-typesense/src/main/java/org/apache/seatunnel/connectors/seatunnel/typesense/sink/TypesenseSinkFactory.java
@@ -54,6 +54,7 @@ public class TypesenseSinkFactory implements TableSinkFactory 
{
                         TypesenseSinkOptions.SCHEMA_SAVE_MODE, 
TypesenseSinkOptions.DATA_SAVE_MODE)
                 .optional(TypesenseSinkOptions.PRIMARY_KEYS)
                 .optional(TypesenseSinkOptions.KEY_DELIMITER)
+                .optional(TypesenseSinkOptions.KEY_ENCODING)
                 .optional(TypesenseSinkOptions.MAX_BATCH_SIZE)
                 .optional(TypesenseSinkOptions.MAX_RETRY_COUNT)
                 .optional(SinkConnectorCommonOptions.MULTI_TABLE_SINK_REPLICA)
diff --git 
a/seatunnel-connectors-v2/connector-typesense/src/test/java/org/apache/seatunnel/connectors/seatunnel/typesense/serializer/TypesenseRowSerializerTest.java
 
b/seatunnel-connectors-v2/connector-typesense/src/test/java/org/apache/seatunnel/connectors/seatunnel/typesense/serializer/TypesenseRowSerializerTest.java
index cdc7fe66c8..481e64a238 100644
--- 
a/seatunnel-connectors-v2/connector-typesense/src/test/java/org/apache/seatunnel/connectors/seatunnel/typesense/serializer/TypesenseRowSerializerTest.java
+++ 
b/seatunnel-connectors-v2/connector-typesense/src/test/java/org/apache/seatunnel/connectors/seatunnel/typesense/serializer/TypesenseRowSerializerTest.java
@@ -62,4 +62,54 @@ public class TypesenseRowSerializerTest {
         String data = "{\"name\":\"jack\",\"id\":\"0001\"}";
         Assertions.assertEquals(typesenseRowSerializer.serializeRow(row), 
data);
     }
+
+    @Test
+    public void testCompositeKeyEncodingPreservesLegacyIdsByDefault() {
+        SeaTunnelRowType schema =
+                new SeaTunnelRowType(
+                        new String[] {"first", "second"},
+                        new SeaTunnelDataType[] {STRING_TYPE, STRING_TYPE});
+        SeaTunnelRow left = new SeaTunnelRow(new Object[] {"1_1", "2"});
+        SeaTunnelRow right = new SeaTunnelRow(new Object[] {"1", "1_2"});
+        TypesenseRowSerializer legacy = serializer(schema, null);
+        TypesenseRowSerializer safe = serializer(schema, "LENGTH_PREFIXED");
+
+        Assertions.assertEquals("1_1_2", legacy.serializeRowForDelete(left));
+        Assertions.assertEquals(
+                legacy.serializeRowForDelete(left), 
legacy.serializeRowForDelete(right));
+        Assertions.assertEquals("3:1_11:2", safe.serializeRowForDelete(left));
+        Assertions.assertEquals("1:13:1_2", safe.serializeRowForDelete(right));
+        
Assertions.assertTrue(safe.serializeRow(left).contains("\"id\":\"3:1_11:2\""));
+        
Assertions.assertTrue(safe.serializeRow(right).contains("\"id\":\"1:13:1_2\""));
+    }
+
+    @Test
+    public void testSingleKeyEncodingKeepsTheExistingId() {
+        SeaTunnelRowType schema =
+                new SeaTunnelRowType(
+                        new String[] {"first", "second"},
+                        new SeaTunnelDataType[] {STRING_TYPE, STRING_TYPE});
+        Map<String, Object> confMap = new HashMap<>();
+        confMap.put(TypesenseBaseOptions.COLLECTION.key(), "id_probe");
+        confMap.put(TypesenseSinkOptions.PRIMARY_KEYS.key(), 
Arrays.asList("first"));
+        confMap.put(TypesenseSinkOptions.KEY_ENCODING.key(), 
"LENGTH_PREFIXED");
+        TypesenseRowSerializer serializer =
+                new TypesenseRowSerializer(
+                        new CollectionInfo("id_probe", 
ReadonlyConfig.fromMap(confMap)), schema);
+
+        Assertions.assertEquals(
+                "1_1",
+                serializer.serializeRowForDelete(new SeaTunnelRow(new Object[] 
{"1_1", "2"})));
+    }
+
+    private static TypesenseRowSerializer serializer(SeaTunnelRowType schema, 
String encoding) {
+        Map<String, Object> confMap = new HashMap<>();
+        confMap.put(TypesenseBaseOptions.COLLECTION.key(), "id_probe");
+        confMap.put(TypesenseSinkOptions.PRIMARY_KEYS.key(), 
Arrays.asList("first", "second"));
+        if (encoding != null) {
+            confMap.put(TypesenseSinkOptions.KEY_ENCODING.key(), encoding);
+        }
+        return new TypesenseRowSerializer(
+                new CollectionInfo("id_probe", 
ReadonlyConfig.fromMap(confMap)), schema);
+    }
 }
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-easysearch-e2e/src/test/java/org/apache/seatunnel/e2e/connector/easysearch/EasysearchIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-easysearch-e2e/src/test/java/org/apache/seatunnel/e2e/connector/easysearch/EasysearchIT.java
index 8c279e65b7..5414057407 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-easysearch-e2e/src/test/java/org/apache/seatunnel/e2e/connector/easysearch/EasysearchIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-easysearch-e2e/src/test/java/org/apache/seatunnel/e2e/connector/easysearch/EasysearchIT.java
@@ -166,6 +166,27 @@ public class EasysearchIT extends TestSuiteBase implements 
TestResource {
                                         mapTestDatasetForDSL(), 
readSinkData()));
     }
 
+    @TestTemplate
+    public void 
testCompositeKeyEncodingAvoidsDocumentIdCollisions(TestContainer container)
+            throws Exception {
+        Container.ExecResult result =
+                
container.executeJob("/easysearch/easysearch_composite_key_encoding.conf");
+        Assertions.assertEquals(0, result.getExitCode());
+
+        Awaitility.given()
+                .ignoreExceptions()
+                .pollInterval(1L, TimeUnit.SECONDS)
+                .atMost(INDEX_REFRESH_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+                .untilAsserted(
+                        () ->
+                                Assertions.assertEquals(
+                                        2L,
+                                        easysearchClient
+                                                
.getIndexDocsCount("st_id_encoding")
+                                                .get(0)
+                                                .getDocsCount()));
+    }
+
     @TestTemplate
     public void testEasysearchWithSaveMode(TestContainer container)
             throws IOException, InterruptedException {
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-easysearch-e2e/src/test/resources/easysearch/easysearch_composite_key_encoding.conf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-easysearch-e2e/src/test/resources/easysearch/easysearch_composite_key_encoding.conf
new file mode 100644
index 0000000000..6044166315
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-easysearch-e2e/src/test/resources/easysearch/easysearch_composite_key_encoding.conf
@@ -0,0 +1,50 @@
+#
+# 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 {
+      fields {
+        first = string
+        second = string
+        marker = string
+      }
+    }
+    rows = [
+      { kind = INSERT, fields = ["1_1", "2", "left"] },
+      { kind = INSERT, fields = ["1", "1_2", "right"] }
+    ]
+  }
+}
+
+sink {
+  Easysearch {
+    hosts = ["https://e2e_easysearch:9200";]
+    username = "admin"
+    password = "admin"
+    tls_verify_certificate = false
+    tls_verify_hostname = false
+    index = "st_id_encoding"
+    primary_keys = ["first", "second"]
+    key_encoding = "LENGTH_PREFIXED"
+  }
+}
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-typesense-e2e/src/test/java/org/apache/seatunnel/e2e/connector/typesense/TypesenseIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-typesense-e2e/src/test/java/org/apache/seatunnel/e2e/connector/typesense/TypesenseIT.java
index db1e6bc26c..c442aa9404 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-typesense-e2e/src/test/java/org/apache/seatunnel/e2e/connector/typesense/TypesenseIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-typesense-e2e/src/test/java/org/apache/seatunnel/e2e/connector/typesense/TypesenseIT.java
@@ -130,6 +130,15 @@ public class TypesenseIT extends TestSuiteBase implements 
TestResource {
         Assertions.assertEquals(typesenseClient.search(sinkCollection, null, 
0).getFound(), 5);
     }
 
+    @TestTemplate
+    public void 
testCompositeKeyEncodingAvoidsDocumentIdCollisions(TestContainer container)
+            throws Exception {
+        Container.ExecResult result =
+                
container.executeJob("/fake_to_typesense_composite_key_encoding.conf");
+        Assertions.assertEquals(0, result.getExitCode());
+        Assertions.assertEquals(2, typesenseClient.search(sinkCollection, 
null, 0).getFound());
+    }
+
     @TestTemplate
     public void testFakeToTypesenseWithRecreateSchema(TestContainer container) 
throws Exception {
         List<Field> fields = new ArrayList<>();
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-typesense-e2e/src/test/resources/fake_to_typesense_composite_key_encoding.conf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-typesense-e2e/src/test/resources/fake_to_typesense_composite_key_encoding.conf
new file mode 100644
index 0000000000..9cd03af206
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-typesense-e2e/src/test/resources/fake_to_typesense_composite_key_encoding.conf
@@ -0,0 +1,47 @@
+#
+# 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 {
+      fields {
+        first = string
+        second = string
+        marker = string
+      }
+    }
+    rows = [
+      { kind = INSERT, fields = ["1_1", "2", "left"] },
+      { kind = INSERT, fields = ["1", "1_2", "right"] }
+    ]
+  }
+}
+
+sink {
+  Typesense {
+    hosts = ["e2e_typesense:8108"]
+    collection = "typesense_test_collection"
+    api_key = "xyz"
+    primary_keys = ["first", "second"]
+    key_encoding = "LENGTH_PREFIXED"
+  }
+}

Reply via email to