Browse Source

ESQL: Tests for large concat and many evals (#100159)

Nik Everett 2 years ago
parent
commit
9620512a89

+ 3 - 0
distribution/src/config/log4j2.properties

@@ -52,6 +52,9 @@ appender.rolling_old.strategy.action.condition.nested_condition.type = IfAccumul
 appender.rolling_old.strategy.action.condition.nested_condition.exceeds = 2GB
 ################################################
 
+logger.esql.name = org.elasticsearch.xpack.esql
+logger.esql.level = trace
+
 rootLogger.level = info
 rootLogger.appenderRef.console.ref = console
 rootLogger.appenderRef.rolling.ref = rolling

+ 1 - 1
docs/reference/esql/functions/signature/case.svg

@@ -1 +1 @@
-<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="440" height="76" viewbox="0 0 440 76"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 46h5m68 0h10m32 0h10m68 0h50m-5 0q-5 0-5-5v-26q0-5 5-5h120q5 0 5 5v26q0 5-5 5m-83 0h10m68 0h40m-185 0q5 0 5 5v10q0 5 5 5h160q5 0 5-5v-10q0-5 5-5m5 0h10m32 0h5"/><rect class="s" x="5" y="20" width="68" height="36"/><text class="k" x="15" y="46">CASE</text><rect class="s" x="83" y="20" width="32" height="36" rx="7"/><text class="syn" x="93" y="46">(</text><rect class="s" x="125" y="20" width="68" height="36" rx="7"/><text class="k" x="135" y="46">arg1</text><rect class="s" x="243" y="20" width="32" height="36" rx="7"/><text class="syn" x="253" y="46">,</text><rect class="s" x="285" y="20" width="68" height="36" rx="7"/><text class="k" x="295" y="46">arg2</text><rect class="s" x="403" y="20" width="32" height="36" rx="7"/><text class="syn" x="413" y="46">)</text></svg>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="360" height="46" viewbox="0 0 360 46"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 31h5m68 0h10m32 0h10m68 0h10m32 0h10m68 0h10m32 0h5"/><rect class="s" x="5" y="5" width="68" height="36"/><text class="k" x="15" y="31">CASE</text><rect class="s" x="83" y="5" width="32" height="36" rx="7"/><text class="syn" x="93" y="31">(</text><rect class="s" x="125" y="5" width="68" height="36" rx="7"/><text class="k" x="135" y="31">arg1</text><rect class="s" x="203" y="5" width="32" height="36" rx="7"/><text class="syn" x="213" y="31">,</text><rect class="s" x="245" y="5" width="68" height="36" rx="7"/><text class="k" x="255" y="31">arg2</text><rect class="s" x="323" y="5" width="32" height="36" rx="7"/><text class="syn" x="333" y="31">)</text></svg>

+ 1 - 1
docs/reference/esql/functions/signature/coalesce.svg

@@ -1 +1 @@
-<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="488" height="76" viewbox="0 0 488 76"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 46h5m116 0h10m32 0h10m68 0h50m-5 0q-5 0-5-5v-26q0-5 5-5h120q5 0 5 5v26q0 5-5 5m-83 0h10m68 0h40m-185 0q5 0 5 5v10q0 5 5 5h160q5 0 5-5v-10q0-5 5-5m5 0h10m32 0h5"/><rect class="s" x="5" y="20" width="116" height="36"/><text class="k" x="15" y="46">COALESCE</text><rect class="s" x="131" y="20" width="32" height="36" rx="7"/><text class="syn" x="141" y="46">(</text><rect class="s" x="173" y="20" width="68" height="36" rx="7"/><text class="k" x="183" y="46">arg1</text><rect class="s" x="291" y="20" width="32" height="36" rx="7"/><text class="syn" x="301" y="46">,</text><rect class="s" x="333" y="20" width="68" height="36" rx="7"/><text class="k" x="343" y="46">arg2</text><rect class="s" x="451" y="20" width="32" height="36" rx="7"/><text class="syn" x="461" y="46">)</text></svg>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="408" height="46" viewbox="0 0 408 46"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 31h5m116 0h10m32 0h10m68 0h10m32 0h10m68 0h10m32 0h5"/><rect class="s" x="5" y="5" width="116" height="36"/><text class="k" x="15" y="31">COALESCE</text><rect class="s" x="131" y="5" width="32" height="36" rx="7"/><text class="syn" x="141" y="31">(</text><rect class="s" x="173" y="5" width="68" height="36" rx="7"/><text class="k" x="183" y="31">arg1</text><rect class="s" x="251" y="5" width="32" height="36" rx="7"/><text class="syn" x="261" y="31">,</text><rect class="s" x="293" y="5" width="68" height="36" rx="7"/><text class="k" x="303" y="31">arg2</text><rect class="s" x="371" y="5" width="32" height="36" rx="7"/><text class="syn" x="381" y="31">)</text></svg>

+ 1 - 1
docs/reference/esql/functions/signature/concat.svg

@@ -1 +1 @@
-<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="464" height="76" viewbox="0 0 464 76"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 46h5m92 0h10m32 0h10m68 0h50m-5 0q-5 0-5-5v-26q0-5 5-5h120q5 0 5 5v26q0 5-5 5m-83 0h10m68 0h40m-185 0q5 0 5 5v10q0 5 5 5h160q5 0 5-5v-10q0-5 5-5m5 0h10m32 0h5"/><rect class="s" x="5" y="20" width="92" height="36"/><text class="k" x="15" y="46">CONCAT</text><rect class="s" x="107" y="20" width="32" height="36" rx="7"/><text class="syn" x="117" y="46">(</text><rect class="s" x="149" y="20" width="68" height="36" rx="7"/><text class="k" x="159" y="46">arg1</text><rect class="s" x="267" y="20" width="32" height="36" rx="7"/><text class="syn" x="277" y="46">,</text><rect class="s" x="309" y="20" width="68" height="36" rx="7"/><text class="k" x="319" y="46">arg2</text><rect class="s" x="427" y="20" width="32" height="36" rx="7"/><text class="syn" x="437" y="46">)</text></svg>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="384" height="46" viewbox="0 0 384 46"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 31h5m92 0h10m32 0h10m68 0h10m32 0h10m68 0h10m32 0h5"/><rect class="s" x="5" y="5" width="92" height="36"/><text class="k" x="15" y="31">CONCAT</text><rect class="s" x="107" y="5" width="32" height="36" rx="7"/><text class="syn" x="117" y="31">(</text><rect class="s" x="149" y="5" width="68" height="36" rx="7"/><text class="k" x="159" y="31">arg1</text><rect class="s" x="227" y="5" width="32" height="36" rx="7"/><text class="syn" x="237" y="31">,</text><rect class="s" x="269" y="5" width="68" height="36" rx="7"/><text class="k" x="279" y="31">arg2</text><rect class="s" x="347" y="5" width="32" height="36" rx="7"/><text class="syn" x="357" y="31">)</text></svg>

+ 1 - 1
docs/reference/esql/functions/signature/date_parse.svg

@@ -1 +1 @@
-<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="432" height="46" viewbox="0 0 432 46"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 31h5m140 0h10m32 0h10m68 0h10m32 0h10m68 0h10m32 0h5"/><rect class="s" x="5" y="5" width="140" height="36"/><text class="k" x="15" y="31">DATE_PARSE</text><rect class="s" x="155" y="5" width="32" height="36" rx="7"/><text class="syn" x="165" y="31">(</text><rect class="s" x="197" y="5" width="68" height="36" rx="7"/><text class="k" x="207" y="31">arg1</text><rect class="s" x="275" y="5" width="32" height="36" rx="7"/><text class="syn" x="285" y="31">,</text><rect class="s" x="317" y="5" width="68" height="36" rx="7"/><text class="k" x="327" y="31">arg2</text><rect class="s" x="395" y="5" width="32" height="36" rx="7"/><text class="syn" x="405" y="31">)</text></svg>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="588" height="46" viewbox="0 0 588 46"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 31h5m140 0h10m32 0h10m152 0h10m32 0h10m140 0h10m32 0h5"/><rect class="s" x="5" y="5" width="140" height="36"/><text class="k" x="15" y="31">DATE_PARSE</text><rect class="s" x="155" y="5" width="32" height="36" rx="7"/><text class="syn" x="165" y="31">(</text><rect class="s" x="197" y="5" width="152" height="36" rx="7"/><text class="k" x="207" y="31">datePattern</text><rect class="s" x="359" y="5" width="32" height="36" rx="7"/><text class="syn" x="369" y="31">,</text><rect class="s" x="401" y="5" width="140" height="36" rx="7"/><text class="k" x="411" y="31">dateString</text><rect class="s" x="551" y="5" width="32" height="36" rx="7"/><text class="syn" x="561" y="31">)</text></svg>

+ 1 - 1
docs/reference/esql/functions/signature/greatest.svg

@@ -1 +1 @@
-<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="500" height="76" viewbox="0 0 500 76"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 46h5m116 0h10m32 0h10m80 0h50m-5 0q-5 0-5-5v-26q0-5 5-5h120q5 0 5 5v26q0 5-5 5m-83 0h10m68 0h40m-185 0q5 0 5 5v10q0 5 5 5h160q5 0 5-5v-10q0-5 5-5m5 0h10m32 0h5"/><rect class="s" x="5" y="20" width="116" height="36"/><text class="k" x="15" y="46">GREATEST</text><rect class="s" x="131" y="20" width="32" height="36" rx="7"/><text class="syn" x="141" y="46">(</text><rect class="s" x="173" y="20" width="80" height="36" rx="7"/><text class="k" x="183" y="46">first</text><rect class="s" x="303" y="20" width="32" height="36" rx="7"/><text class="syn" x="313" y="46">,</text><rect class="s" x="345" y="20" width="68" height="36" rx="7"/><text class="k" x="355" y="46">rest</text><rect class="s" x="463" y="20" width="32" height="36" rx="7"/><text class="syn" x="473" y="46">)</text></svg>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="420" height="46" viewbox="0 0 420 46"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 31h5m116 0h10m32 0h10m80 0h10m32 0h10m68 0h10m32 0h5"/><rect class="s" x="5" y="5" width="116" height="36"/><text class="k" x="15" y="31">GREATEST</text><rect class="s" x="131" y="5" width="32" height="36" rx="7"/><text class="syn" x="141" y="31">(</text><rect class="s" x="173" y="5" width="80" height="36" rx="7"/><text class="k" x="183" y="31">first</text><rect class="s" x="263" y="5" width="32" height="36" rx="7"/><text class="syn" x="273" y="31">,</text><rect class="s" x="305" y="5" width="68" height="36" rx="7"/><text class="k" x="315" y="31">rest</text><rect class="s" x="383" y="5" width="32" height="36" rx="7"/><text class="syn" x="393" y="31">)</text></svg>

+ 1 - 1
docs/reference/esql/functions/signature/least.svg

@@ -1 +1 @@
-<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="464" height="76" viewbox="0 0 464 76"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 46h5m80 0h10m32 0h10m80 0h50m-5 0q-5 0-5-5v-26q0-5 5-5h120q5 0 5 5v26q0 5-5 5m-83 0h10m68 0h40m-185 0q5 0 5 5v10q0 5 5 5h160q5 0 5-5v-10q0-5 5-5m5 0h10m32 0h5"/><rect class="s" x="5" y="20" width="80" height="36"/><text class="k" x="15" y="46">LEAST</text><rect class="s" x="95" y="20" width="32" height="36" rx="7"/><text class="syn" x="105" y="46">(</text><rect class="s" x="137" y="20" width="80" height="36" rx="7"/><text class="k" x="147" y="46">first</text><rect class="s" x="267" y="20" width="32" height="36" rx="7"/><text class="syn" x="277" y="46">,</text><rect class="s" x="309" y="20" width="68" height="36" rx="7"/><text class="k" x="319" y="46">rest</text><rect class="s" x="427" y="20" width="32" height="36" rx="7"/><text class="syn" x="437" y="46">)</text></svg>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="384" height="46" viewbox="0 0 384 46"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 31h5m80 0h10m32 0h10m80 0h10m32 0h10m68 0h10m32 0h5"/><rect class="s" x="5" y="5" width="80" height="36"/><text class="k" x="15" y="31">LEAST</text><rect class="s" x="95" y="5" width="32" height="36" rx="7"/><text class="syn" x="105" y="31">(</text><rect class="s" x="137" y="5" width="80" height="36" rx="7"/><text class="k" x="147" y="31">first</text><rect class="s" x="227" y="5" width="32" height="36" rx="7"/><text class="syn" x="237" y="31">,</text><rect class="s" x="269" y="5" width="68" height="36" rx="7"/><text class="k" x="279" y="31">rest</text><rect class="s" x="347" y="5" width="32" height="36" rx="7"/><text class="syn" x="357" y="31">)</text></svg>

+ 9 - 3
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/ProjectOperator.java

@@ -70,10 +70,17 @@ public class ProjectOperator extends AbstractPageMappingOperator {
             var block = page.getBlock(source);
             blocks[b++] = block;
         }
-        // iterate the blocks to see which one isn't used
+        closeUnused(page, blocks);
+        return new Page(page.getPositionCount(), blocks);
+    }
+
+    /**
+     * Close all {@link Block}s that are in {@code page} but are not in {@code blocks}.
+     */
+    public static void closeUnused(Page page, Block[] blocks) {
         List<Releasable> blocksToRelease = new ArrayList<>();
 
-        for (int i = 0; i < blockCount; i++) {
+        for (int i = 0; i < page.getBlockCount(); i++) {
             boolean used = false;
             var current = page.getBlock(i);
             for (int j = 0; j < blocks.length; j++) {
@@ -87,7 +94,6 @@ public class ProjectOperator extends AbstractPageMappingOperator {
             }
         }
         Releasables.close(blocksToRelease);
-        return new Page(page.getPositionCount(), blocks);
     }
 
     @Override

+ 99 - 4
x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/HeapAttackIT.java

@@ -18,17 +18,23 @@ import org.elasticsearch.core.TimeValue;
 import org.elasticsearch.test.ListMatcher;
 import org.elasticsearch.test.rest.ESRestTestCase;
 import org.elasticsearch.xcontent.json.JsonXContent;
+import org.elasticsearch.xpack.esql.qa.rest.EsqlSpecTestCase;
+import org.junit.After;
+import org.junit.Before;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 import static org.elasticsearch.test.ListMatcher.matchesList;
 import static org.elasticsearch.test.MapMatcher.assertMap;
 import static org.elasticsearch.test.MapMatcher.matchesMap;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
 
 /**
  * Tests that run ESQL queries that have, in the past, used so much memory they
@@ -58,7 +64,11 @@ public class HeapAttackIT extends ESRestTestCase {
      */
     public void testSortByManyLongsTooMuchMemory() throws IOException {
         initManyLongs();
-        ResponseException e = expectThrows(ResponseException.class, () -> sortByManyLongs(5000));
+        assertCircuitBreaks(() -> sortByManyLongs(5000));
+    }
+
+    private void assertCircuitBreaks(ThrowingRunnable r) throws IOException {
+        ResponseException e = expectThrows(ResponseException.class, r);
         Map<?, ?> map = XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(e.getResponse().getEntity()), false);
         assertMap(
             map,
@@ -139,6 +149,73 @@ public class HeapAttackIT extends ESRestTestCase {
         return query.append("\\n");
     }
 
+    public void testSmallConcat() throws IOException {
+        initSingleDocIndex();
+        Map<?, ?> map = XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(concat(2).getEntity()), false);
+        ListMatcher columns = matchesList().item(matchesMap().entry("name", "a").entry("type", "long"))
+            .item(matchesMap().entry("name", "str").entry("type", "keyword"));
+        ListMatcher values = matchesList().item(List.of(1, "1".repeat(100)));
+        assertMap(map, matchesMap().entry("columns", columns).entry("values", values));
+    }
+
+    public void testHugeConcat() throws IOException {
+        initSingleDocIndex();
+        assertCircuitBreaks(() -> concat(10));
+    }
+
+    private Response concat(int evals) throws IOException {
+        StringBuilder query = new StringBuilder();
+        query.append("{\"query\":\"FROM single | EVAL str = TO_STRING(a)");
+        for (int e = 0; e < evals; e++) {
+            query.append("\n| EVAL str=CONCAT(")
+                .append(IntStream.range(0, 10).mapToObj(i -> "str").collect(Collectors.joining(", ")))
+                .append(")");
+        }
+        query.append("\"}");
+        Request request = new Request("POST", "/_query");
+        request.setJsonEntity(query.toString().replace("\n", "\\n"));
+        return client().performRequest(request);
+    }
+
+    public void testManyEval() throws IOException {
+        initManyLongs();
+        Map<?, ?> map = XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(manyEval(1).getEntity()), false);
+        ListMatcher columns = matchesList();
+        columns = columns.item(matchesMap().entry("name", "a").entry("type", "long"));
+        columns = columns.item(matchesMap().entry("name", "b").entry("type", "long"));
+        columns = columns.item(matchesMap().entry("name", "c").entry("type", "long"));
+        columns = columns.item(matchesMap().entry("name", "d").entry("type", "long"));
+        columns = columns.item(matchesMap().entry("name", "e").entry("type", "long"));
+        for (int i = 0; i < 10; i++) {
+            columns = columns.item(matchesMap().entry("name", "i0" + i).entry("type", "long"));
+        }
+        assertMap(map, matchesMap().entry("columns", columns).entry("values", hasSize(10_000)));
+    }
+
+    @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/99826")
+    public void testTooManyEval() throws IOException {
+        initManyLongs();
+        assertCircuitBreaks(() -> manyEval(1000));
+    }
+
+    private Response manyEval(int evalLines) throws IOException {
+        StringBuilder query = new StringBuilder();
+        query.append("{\"query\":\"FROM manylongs");
+        for (int e = 0; e < evalLines; e++) {
+            query.append("\n| EVAL ");
+            for (int i = 0; i < 10; i++) {
+                if (i != 0) {
+                    query.append(", ");
+                }
+                query.append("i").append(e).append(i).append(" = ").append(e * 10 + i).append(" + a + b");
+            }
+        }
+        query.append("\n| LIMIT 10000\"}");
+        Request request = new Request("POST", "/_query");
+        request.setJsonEntity(query.toString().replace("\n", "\\n"));
+        return client().performRequest(request);
+    }
+
     private void initManyLongs() throws IOException {
         logger.info("loading many documents with longs");
         StringBuilder bulk = new StringBuilder();
@@ -156,14 +233,26 @@ public class HeapAttackIT extends ESRestTestCase {
                 }
             }
         }
-        Request request = new Request("POST", "/manylongs/_bulk");
+        initIndex("manylongs", bulk.toString());
+    }
+
+    private void initSingleDocIndex() throws IOException {
+        logger.info("loading many documents with a single document");
+        initIndex("single", """
+            {"create":{}}
+            {"a":1}
+            """);
+    }
+
+    private void initIndex(String name, String bulk) throws IOException {
+        Request request = new Request("POST", "/" + name + "/_bulk");
         request.addParameter("refresh", "true");
         request.addParameter("filter_path", "errors");
         request.setJsonEntity(bulk.toString());
         Response response = client().performRequest(request);
         assertThat(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8), equalTo("{\"errors\":false}"));
 
-        request = new Request("POST", "/manylongs/_forcemerge");
+        request = new Request("POST", "/" + name + "/_forcemerge");
         request.addParameter("max_num_segments", "1");
         response = client().performRequest(request);
         assertThat(
@@ -171,11 +260,17 @@ public class HeapAttackIT extends ESRestTestCase {
             equalTo("{\"_shards\":{\"total\":2,\"successful\":1,\"failed\":0}}")
         );
 
-        request = new Request("POST", "/manylongs/_refresh");
+        request = new Request("POST", "/" + name + "/_refresh");
         response = client().performRequest(request);
         assertThat(
             EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8),
             equalTo("{\"_shards\":{\"total\":2,\"successful\":1,\"failed\":0}}")
         );
     }
+
+    @Before
+    @After
+    public void assertRequestBreakerEmpty() throws Exception {
+        EsqlSpecTestCase.assertRequestBreakerEmpty();
+    }
 }

+ 2 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlanner.java

@@ -30,6 +30,7 @@ import org.elasticsearch.compute.operator.MvExpandOperator;
 import org.elasticsearch.compute.operator.Operator;
 import org.elasticsearch.compute.operator.Operator.OperatorFactory;
 import org.elasticsearch.compute.operator.OutputOperator.OutputOperatorFactory;
+import org.elasticsearch.compute.operator.ProjectOperator;
 import org.elasticsearch.compute.operator.RowOperator.RowOperatorFactory;
 import org.elasticsearch.compute.operator.ShowOperator;
 import org.elasticsearch.compute.operator.SinkOperator;
@@ -333,6 +334,7 @@ public class LocalExecutionPlanner {
             for (int i = 0; i < blocks.length; i++) {
                 blocks[i] = p.getBlock(mappedPosition[i]);
             }
+            ProjectOperator.closeUnused(p, blocks);
             return new Page(blocks);
         } : Function.identity();