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 b1c53562b0 [Improve][connector-elasticsearchv2]Optimize Elasticsearch 
source che… (#10446)
b1c53562b0 is described below

commit b1c53562b06be6dd71131dffd73b0439aed9640b
Author: cosmosni <[email protected]>
AuthorDate: Sun Jul 19 09:55:03 2026 +0800

    [Improve][connector-elasticsearchv2]Optimize Elasticsearch source che… 
(#10446)
---
 .../elasticsearch/client/EsRestClient.java         |  44 ++++++++-
 .../source/ElasticsearchSourceReader.java          |  30 ++++--
 .../source/ElasticsearchSourceReaderTest.java      | 109 +++++++++++++++++++++
 3 files changed, 173 insertions(+), 10 deletions(-)

diff --git 
a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClient.java
 
b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClient.java
index 6a43db1791..96432b315c 100644
--- 
a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClient.java
+++ 
b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClient.java
@@ -345,6 +345,48 @@ public class EsRestClient implements Closeable {
         }
     }
 
+    /**
+     * Close SQL cursor to release server-side resources.
+     *
+     * @param cursor The SQL cursor to close
+     * @return True if the cursor was successfully closed
+     */
+    public boolean closeSqlCursor(String cursor) {
+        if (StringUtils.isEmpty(cursor)) {
+            log.debug("SQL cursor is empty; skip closing.");
+            return false;
+        }
+
+        String endpoint = "/_sql/close";
+        Request request = new Request("POST", endpoint);
+        Map<String, String> requestBody = new HashMap<>();
+        requestBody.put("cursor", cursor);
+        request.setJsonEntity(JsonUtils.toJsonString(requestBody));
+
+        try {
+            Response response = restClient.performRequest(request);
+            if (response == null) {
+                log.warn("POST {} response null for cursor: {}", endpoint, 
cursor);
+                return false;
+            }
+            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                String entity = EntityUtils.toString(response.getEntity());
+                JsonNode jsonNode = JsonUtils.parseObject(entity);
+                return jsonNode.get("succeeded").asBoolean();
+            } else {
+                log.warn(
+                        "POST {} response status code={} for cursor: {}",
+                        endpoint,
+                        response.getStatusLine().getStatusCode(),
+                        cursor);
+                return false;
+            }
+        } catch (Exception ex) {
+            log.warn("Failed to close SQL cursor: {}", cursor, ex);
+            return false;
+        }
+    }
+
     private ScrollResult getDocsFromSqlResult(
             String endpoint, String requestBody, JsonNode columnNodes) {
         Request request = new Request("POST", endpoint);
@@ -1110,7 +1152,7 @@ public class EsRestClient implements Closeable {
         String updatedPitId = rootNode.has("pit_id") ? 
rootNode.get("pit_id").asText() : pitId;
 
         // Determine if there are more results
-        boolean hasMore = docs.size() > 0 && totalHits > 0 && docs.size() < 
totalHits;
+        boolean hasMore = !docs.isEmpty();
 
         return new PointInTimeResult(updatedPitId, docs, totalHits, 
searchAfter, hasMore);
     }
diff --git 
a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReader.java
 
b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReader.java
index 1faca89f15..020f1767d3 100644
--- 
a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReader.java
+++ 
b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReader.java
@@ -102,16 +102,28 @@ public class ElasticsearchSourceReader
         // SQL client
         if (SearchTypeEnum.SQL.equals(sourceIndexInfo.getSearchType())) {
             log.info("Using SQL query for index: {}", 
sourceIndexInfo.getIndex());
-            ScrollResult scrollResult =
-                    esRestClient.searchBySql(
-                            sourceIndexInfo.getSqlQuery(), 
sourceIndexInfo.getScrollSize());
-
-            outputFromScrollResult(scrollResult, sourceIndexInfo, output, 
deserializer);
-            while (StringUtils.isNotEmpty(scrollResult.getScrollId())) {
-                scrollResult =
-                        esRestClient.searchWithSql(
-                                scrollResult.getScrollId(), 
scrollResult.getColumnNodes());
+            String cursor = null;
+            try {
+                ScrollResult scrollResult =
+                        esRestClient.searchBySql(
+                                sourceIndexInfo.getSqlQuery(), 
sourceIndexInfo.getScrollSize());
+
                 outputFromScrollResult(scrollResult, sourceIndexInfo, output, 
deserializer);
+                cursor = scrollResult.getScrollId();
+                while (StringUtils.isNotEmpty(cursor)) {
+                    scrollResult =
+                            esRestClient.searchWithSql(cursor, 
scrollResult.getColumnNodes());
+                    outputFromScrollResult(scrollResult, sourceIndexInfo, 
output, deserializer);
+                    cursor = scrollResult.getScrollId();
+                }
+            } finally {
+                if (StringUtils.isNotEmpty(cursor)) {
+                    try {
+                        esRestClient.closeSqlCursor(cursor);
+                    } catch (Exception e) {
+                        log.warn("Failed to close SQL cursor: {}", cursor, e);
+                    }
+                }
             }
         } else {
             // Check if we should use PIT API
diff --git 
a/seatunnel-connectors-v2/connector-elasticsearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReaderTest.java
 
b/seatunnel-connectors-v2/connector-elasticsearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReaderTest.java
new file mode 100644
index 0000000000..7521af1e7d
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-elasticsearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReaderTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.elasticsearch.source;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.source.Collector;
+import org.apache.seatunnel.api.source.SourceReader;
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
+import org.apache.seatunnel.api.table.catalog.PhysicalColumn;
+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.SeaTunnelRow;
+import 
org.apache.seatunnel.connectors.seatunnel.elasticsearch.client.EsRestClient;
+import 
org.apache.seatunnel.connectors.seatunnel.elasticsearch.config.ElasticsearchConfig;
+import 
org.apache.seatunnel.connectors.seatunnel.elasticsearch.config.SearchTypeEnum;
+import 
org.apache.seatunnel.connectors.seatunnel.elasticsearch.dto.source.ScrollResult;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ElasticsearchSourceReaderTest {
+
+    @Test
+    public void testCloseSqlCursorWhenSearchWithSqlThrowsException() throws 
Exception {
+        SourceReader.Context context = mock(SourceReader.Context.class);
+        ElasticsearchSourceReader reader =
+                new ElasticsearchSourceReader(
+                        context, 
ReadonlyConfig.fromMap(Collections.emptyMap()));
+
+        EsRestClient esRestClient = mock(EsRestClient.class);
+        injectEsRestClient(reader, esRestClient);
+
+        ScrollResult firstPage = new ScrollResult();
+        firstPage.setScrollId("cursor-1");
+        firstPage.setDocs(Collections.emptyList());
+
+        when(esRestClient.searchBySql(anyString(), 
anyInt())).thenReturn(firstPage);
+        when(esRestClient.searchWithSql(eq("cursor-1"), any()))
+                .thenThrow(new RuntimeException("mock sql scroll exception"));
+
+        reader.addSplits(
+                Collections.singletonList(new ElasticsearchSourceSplit("0", 
createSqlSplit())));
+
+        Collector<SeaTunnelRow> collector = mock(Collector.class);
+        when(collector.getCheckpointLock()).thenReturn(new Object());
+
+        Assertions.assertThrows(RuntimeException.class, () -> 
reader.pollNext(collector));
+
+        verify(esRestClient).closeSqlCursor("cursor-1");
+    }
+
+    private static ElasticsearchConfig createSqlSplit() {
+        ElasticsearchConfig config = new ElasticsearchConfig();
+        config.setIndex("test_index");
+        config.setSqlQuery("SELECT id FROM test_index");
+        config.setScrollSize(100);
+        config.setSearchType(SearchTypeEnum.SQL);
+        config.setSource(Collections.singletonList("id"));
+        config.setCatalogTable(createCatalogTable());
+        return config;
+    }
+
+    private static CatalogTable createCatalogTable() {
+        return CatalogTable.of(
+                TableIdentifier.of("test_catalog", "test_database", 
"test_table"),
+                TableSchema.builder()
+                        .column(
+                                PhysicalColumn.of(
+                                        "id", BasicType.INT_TYPE, (Long) null, 
true, null, ""))
+                        .build(),
+                Collections.emptyMap(),
+                Collections.emptyList(),
+                "");
+    }
+
+    private static void injectEsRestClient(ElasticsearchSourceReader reader, 
EsRestClient client)
+            throws Exception {
+        Field field = 
ElasticsearchSourceReader.class.getDeclaredField("esRestClient");
+        field.setAccessible(true);
+        field.set(reader, client);
+    }
+}

Reply via email to