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

cgivre pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/drill.git


The following commit(s) were added to refs/heads/master by this push:
     new 051b1649a0 DRILL-8239: Convert JSON UDF to EVF (#2567)
051b1649a0 is described below

commit 051b1649a097410150a698bb29d6d8f29f0e1762
Author: Charles S. Givre <[email protected]>
AuthorDate: Thu Aug 20 06:45:19 2026 -0400

    DRILL-8239: Convert JSON UDF to EVF (#2567)
    
    DRILL-8239: Convert convert_fromJSON UDF to EVF
---
 .../exec/expr/fn/DrillComplexWriterFuncHolder.java |   2 +-
 .../exec/expr/fn/impl/conv/JsonConvertFrom.java    | 298 ++++++++-------------
 .../exec/expr/fn/impl/conv/JsonConverterUtils.java | 181 +++++++++++++
 .../physical/impl/project/ProjectBatchBuilder.java |   7 +-
 .../physical/impl/project/ProjectRecordBatch.java  | 113 ++++++--
 .../exec/store/json/TestJsonConversionUDF.java     |  27 ++
 .../40658b21-8d70-4eef-ba2f-52881392a6a8/data.json |   1 +
 .../40658b21-8d70-4eef-ba2f-52881392a6a8/meta.json |  15 ++
 .../4bdbeab0-2721-4be8-b92e-2c61f2f8d30e/data.json |   1 +
 .../4bdbeab0-2721-4be8-b92e-2c61f2f8d30e/meta.json |  15 ++
 10 files changed, 447 insertions(+), 213 deletions(-)

diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/DrillComplexWriterFuncHolder.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/DrillComplexWriterFuncHolder.java
index 4f6491c092..8b468019ba 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/DrillComplexWriterFuncHolder.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/DrillComplexWriterFuncHolder.java
@@ -87,7 +87,7 @@ public class DrillComplexWriterFuncHolder extends 
DrillSimpleFuncHolder {
       
classGenerator.getEvalBlock().add(complexWriter.invoke("setPosition").arg(classGenerator.getMappingSet().getValueWriteIndex()));
       sub.decl(classGenerator.getModel()._ref(ComplexWriter.class), 
getReturnValue().getName(), complexWriter);
     } else {
-      
classGenerator.getSetupBlock().add(projBatch.invoke("addLoader").arg(rsLoader));
+      
classGenerator.getSetupBlock().add(projBatch.invoke("addLoader").arg(rsLoader).arg(JExpr.lit(refName)));
       sub.decl(classGenerator.getModel()._ref(ResultSetLoader.class), 
getReturnValue().getName(), rsLoader);
     }
 
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/conv/JsonConvertFrom.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/conv/JsonConvertFrom.java
index 61e64a55dc..a64ce5b2f1 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/conv/JsonConvertFrom.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/conv/JsonConvertFrom.java
@@ -18,7 +18,6 @@
 package org.apache.drill.exec.expr.fn.impl.conv;
 
 
-import io.netty.buffer.DrillBuf;
 import org.apache.drill.exec.expr.DrillSimpleFunc;
 import org.apache.drill.exec.expr.annotations.FunctionTemplate;
 import org.apache.drill.exec.expr.annotations.FunctionTemplate.FunctionScope;
@@ -30,11 +29,13 @@ import 
org.apache.drill.exec.expr.holders.NullableVarBinaryHolder;
 import org.apache.drill.exec.expr.holders.NullableVarCharHolder;
 import org.apache.drill.exec.expr.holders.VarBinaryHolder;
 import org.apache.drill.exec.expr.holders.VarCharHolder;
+import org.apache.drill.exec.physical.resultSet.ResultSetLoader;
 import org.apache.drill.exec.server.options.OptionManager;
 import org.apache.drill.exec.vector.complex.writer.BaseWriter.ComplexWriter;
 
 import javax.inject.Inject;
 
+@SuppressWarnings("unused")
 public class JsonConvertFrom {
 
   private JsonConvertFrom() {
@@ -46,39 +47,31 @@ public class JsonConvertFrom {
     @Param
     VarBinaryHolder in;
 
-    @Inject
-    DrillBuf buffer;
+    @Output
+    ComplexWriter writer;
 
     @Inject
     OptionManager options;
 
+    @Inject
+    ResultSetLoader rsLoader;
+
     @Workspace
-    org.apache.drill.exec.vector.complex.fn.JsonReader jsonReader;
+    org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator 
streamIter;
 
-    @Output
-    ComplexWriter writer;
+    @Workspace
+    org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl jsonLoader;
 
     @Override
     public void setup() {
-      boolean allTextMode = 
options.getBoolean(org.apache.drill.exec.ExecConstants.JSON_ALL_TEXT_MODE);
-      boolean readNumbersAsDouble = 
options.getBoolean(org.apache.drill.exec.ExecConstants.JSON_READ_NUMBERS_AS_DOUBLE);
-
-      jsonReader = new 
org.apache.drill.exec.vector.complex.fn.JsonReader.Builder(buffer)
-          .defaultSchemaPathColumns()
-          .allTextMode(allTextMode)
-          .readNumbersAsDouble(readNumbersAsDouble)
-          .build();
+      streamIter = new 
org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator();
+      rsLoader.startBatch();
     }
 
     @Override
     public void eval() {
-      try {
-        jsonReader.setSource(in.start, in.end, in.buffer);
-        jsonReader.write(writer);
-        buffer = jsonReader.getWorkBuf();
-      } catch (Exception e) {
-        throw new 
org.apache.drill.common.exceptions.DrillRuntimeException("Error while 
converting from JSON. ", e);
-      }
+      jsonLoader = 
org.apache.drill.exec.expr.fn.impl.conv.JsonConverterUtils.convertJson(
+        rsLoader, jsonLoader, options, streamIter, 1, in.start, in.end, 
in.buffer, false, false, false);
     }
   }
 
@@ -94,79 +87,66 @@ public class JsonConvertFrom {
     @Param
     BitHolder readNumbersAsDoubleHolder;
 
+    @Output
+    ComplexWriter writer;
+
+    @Inject
+    OptionManager options;
+
     @Inject
-    DrillBuf buffer;
+    ResultSetLoader rsLoader;
 
     @Workspace
-    org.apache.drill.exec.vector.complex.fn.JsonReader jsonReader;
+    org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator 
streamIter;
 
-    @Output
-    ComplexWriter writer;
+    @Workspace
+    org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl jsonLoader;
 
     @Override
     public void setup() {
-      boolean allTextMode = allTextModeHolder.value == 1;
-      boolean readNumbersAsDouble = readNumbersAsDoubleHolder.value == 1;
-
-      jsonReader = new 
org.apache.drill.exec.vector.complex.fn.JsonReader.Builder(buffer)
-          .defaultSchemaPathColumns()
-          .allTextMode(allTextMode)
-          .readNumbersAsDouble(readNumbersAsDouble)
-          .build();
+      streamIter = new 
org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator();
+      rsLoader.startBatch();
     }
 
     @Override
     public void eval() {
-      try {
-        jsonReader.setSource(in.start, in.end, in.buffer);
-        jsonReader.write(writer);
-        buffer = jsonReader.getWorkBuf();
-      } catch (Exception e) {
-        throw new 
org.apache.drill.common.exceptions.DrillRuntimeException("Error while 
converting from JSON. ", e);
-      }
+      jsonLoader = 
org.apache.drill.exec.expr.fn.impl.conv.JsonConverterUtils.convertJson(
+        rsLoader, jsonLoader, options, streamIter, 1, in.start, in.end, 
in.buffer, true,
+        allTextModeHolder.value == 1, readNumbersAsDoubleHolder.value == 1);
     }
   }
 
-
   @FunctionTemplate(name = "convert_fromJSON", scope = FunctionScope.SIMPLE, 
isRandom = true)
   public static class ConvertFromJsonVarchar implements DrillSimpleFunc {
 
     @Param
     VarCharHolder in;
 
-    @Inject
-    DrillBuf buffer;
+    @Output
+    ComplexWriter writer;
 
     @Inject
     OptionManager options;
 
+    @Inject
+    ResultSetLoader rsLoader;
+
     @Workspace
-    org.apache.drill.exec.vector.complex.fn.JsonReader jsonReader;
+    org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator 
streamIter;
 
-    @Output
-    ComplexWriter writer;
+    @Workspace
+    org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl jsonLoader;
 
     @Override
     public void setup() {
-      boolean allTextMode = 
options.getBoolean(org.apache.drill.exec.ExecConstants.JSON_ALL_TEXT_MODE);
-      boolean readNumbersAsDouble = 
options.getBoolean(org.apache.drill.exec.ExecConstants.JSON_READ_NUMBERS_AS_DOUBLE);
-
-      jsonReader = new 
org.apache.drill.exec.vector.complex.fn.JsonReader.Builder(buffer)
-          .defaultSchemaPathColumns()
-          .allTextMode(allTextMode)
-          .readNumbersAsDouble(readNumbersAsDouble)
-          .build();
+      streamIter = new 
org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator();
+      rsLoader.startBatch();
     }
 
     @Override
     public void eval() {
-      try {
-        jsonReader.setSource(in.start, in.end, in.buffer);
-        jsonReader.write(writer);
-        buffer = jsonReader.getWorkBuf();
-      } catch (Exception e) {
-        throw new 
org.apache.drill.common.exceptions.DrillRuntimeException("Error while 
converting from JSON. ", e);
-      }
+      jsonLoader = 
org.apache.drill.exec.expr.fn.impl.conv.JsonConverterUtils.convertJson(
+        rsLoader, jsonLoader, options, streamIter, 1, in.start, in.end, 
in.buffer, false, false, false);
     }
   }
 
@@ -182,36 +162,32 @@ public class JsonConvertFrom {
     @Param
     BitHolder readNumbersAsDoubleHolder;
 
+    @Output
+    ComplexWriter writer;
+
     @Inject
-    DrillBuf buffer;
+    OptionManager options;
+
+    @Inject
+    ResultSetLoader rsLoader;
 
     @Workspace
-    org.apache.drill.exec.vector.complex.fn.JsonReader jsonReader;
+    org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator 
streamIter;
 
-    @Output
-    ComplexWriter writer;
+    @Workspace
+    org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl jsonLoader;
 
     @Override
     public void setup() {
-      boolean allTextMode = allTextModeHolder.value == 1;
-      boolean readNumbersAsDouble = readNumbersAsDoubleHolder.value == 1;
-
-      jsonReader = new 
org.apache.drill.exec.vector.complex.fn.JsonReader.Builder(buffer)
-          .defaultSchemaPathColumns()
-          .allTextMode(allTextMode)
-          .readNumbersAsDouble(readNumbersAsDouble)
-          .build();
+      streamIter = new 
org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator();
+      rsLoader.startBatch();
     }
 
     @Override
     public void eval() {
-      try {
-        jsonReader.setSource(in.start, in.end, in.buffer);
-        jsonReader.write(writer);
-        buffer = jsonReader.getWorkBuf();
-      } catch (Exception e) {
-        throw new 
org.apache.drill.common.exceptions.DrillRuntimeException("Error while 
converting from JSON. ", e);
-      }
+      jsonLoader = 
org.apache.drill.exec.expr.fn.impl.conv.JsonConverterUtils.convertJson(
+        rsLoader, jsonLoader, options, streamIter, 1, in.start, in.end, 
in.buffer, true,
+        allTextModeHolder.value == 1, readNumbersAsDoubleHolder.value == 1);
     }
   }
 
@@ -221,47 +197,31 @@ public class JsonConvertFrom {
     @Param
     NullableVarBinaryHolder in;
 
-    @Inject
-    DrillBuf buffer;
+    @Output
+    ComplexWriter writer;
 
     @Inject
     OptionManager options;
 
+    @Inject
+    ResultSetLoader rsLoader;
+
     @Workspace
-    org.apache.drill.exec.vector.complex.fn.JsonReader jsonReader;
+    org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator 
streamIter;
 
-    @Output
-    ComplexWriter writer;
+    @Workspace
+    org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl jsonLoader;
 
     @Override
     public void setup() {
-      boolean allTextMode = 
options.getBoolean(org.apache.drill.exec.ExecConstants.JSON_ALL_TEXT_MODE);
-      boolean readNumbersAsDouble = 
options.getBoolean(org.apache.drill.exec.ExecConstants.JSON_READ_NUMBERS_AS_DOUBLE);
-
-      jsonReader = new 
org.apache.drill.exec.vector.complex.fn.JsonReader.Builder(buffer)
-          .defaultSchemaPathColumns()
-          .allTextMode(allTextMode)
-          .readNumbersAsDouble(readNumbersAsDouble)
-          .build();
+      streamIter = new 
org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator();
+      rsLoader.startBatch();
     }
 
     @Override
     public void eval() {
-      if (in.isSet == 0) {
-        // Return empty map
-        org.apache.drill.exec.vector.complex.writer.BaseWriter.MapWriter 
mapWriter = writer.rootAsMap();
-        mapWriter.start();
-        mapWriter.end();
-        return;
-      }
-
-      try {
-        jsonReader.setSource(in.start, in.end, in.buffer);
-        jsonReader.write(writer);
-        buffer = jsonReader.getWorkBuf();
-      } catch (Exception e) {
-        throw new 
org.apache.drill.common.exceptions.DrillRuntimeException("Error while 
converting from JSON. ", e);
-      }
+      jsonLoader = 
org.apache.drill.exec.expr.fn.impl.conv.JsonConverterUtils.convertJson(
+        rsLoader, jsonLoader, options, streamIter, in.isSet, in.start, in.end, 
in.buffer, false, false, false);
     }
   }
 
@@ -277,44 +237,32 @@ public class JsonConvertFrom {
     @Param
     BitHolder readNumbersAsDoubleHolder;
 
+    @Output
+    ComplexWriter writer;
+
+    @Inject
+    OptionManager options;
+
     @Inject
-    DrillBuf buffer;
+    ResultSetLoader rsLoader;
 
     @Workspace
-    org.apache.drill.exec.vector.complex.fn.JsonReader jsonReader;
+    org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator 
streamIter;
 
-    @Output
-    ComplexWriter writer;
+    @Workspace
+    org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl jsonLoader;
 
     @Override
     public void setup() {
-      boolean allTextMode = allTextModeHolder.value == 1;
-      boolean readNumbersAsDouble = readNumbersAsDoubleHolder.value == 1;
-
-      jsonReader = new 
org.apache.drill.exec.vector.complex.fn.JsonReader.Builder(buffer)
-          .defaultSchemaPathColumns()
-          .allTextMode(allTextMode)
-          .readNumbersAsDouble(readNumbersAsDouble)
-          .build();
+      streamIter = new 
org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator();
+      rsLoader.startBatch();
     }
 
     @Override
     public void eval() {
-      if (in.isSet == 0) {
-        // Return empty map
-        org.apache.drill.exec.vector.complex.writer.BaseWriter.MapWriter 
mapWriter = writer.rootAsMap();
-        mapWriter.start();
-        mapWriter.end();
-        return;
-      }
-
-      try {
-        jsonReader.setSource(in.start, in.end, in.buffer);
-        jsonReader.write(writer);
-        buffer = jsonReader.getWorkBuf();
-      } catch (Exception e) {
-        throw new 
org.apache.drill.common.exceptions.DrillRuntimeException("Error while 
converting from JSON. ", e);
-      }
+      jsonLoader = 
org.apache.drill.exec.expr.fn.impl.conv.JsonConverterUtils.convertJson(
+        rsLoader, jsonLoader, options, streamIter, in.isSet, in.start, in.end, 
in.buffer, true,
+        allTextModeHolder.value == 1, readNumbersAsDoubleHolder.value == 1);
     }
   }
 
@@ -324,46 +272,31 @@ public class JsonConvertFrom {
     @Param
     NullableVarCharHolder in;
 
-    @Inject
-    DrillBuf buffer;
+    @Output
+    ComplexWriter writer;
 
     @Inject
     OptionManager options;
 
+    @Inject
+    ResultSetLoader rsLoader;
+
     @Workspace
-    org.apache.drill.exec.vector.complex.fn.JsonReader jsonReader;
+    org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator 
streamIter;
 
-    @Output ComplexWriter writer;
+    @Workspace
+    org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl jsonLoader;
 
     @Override
     public void setup() {
-      boolean allTextMode = 
options.getBoolean(org.apache.drill.exec.ExecConstants.JSON_ALL_TEXT_MODE);
-      boolean readNumbersAsDouble = 
options.getBoolean(org.apache.drill.exec.ExecConstants.JSON_READ_NUMBERS_AS_DOUBLE);
-
-      jsonReader = new 
org.apache.drill.exec.vector.complex.fn.JsonReader.Builder(buffer)
-          .defaultSchemaPathColumns()
-          .allTextMode(allTextMode)
-          .readNumbersAsDouble(readNumbersAsDouble)
-          .build();
+      streamIter = new 
org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator();
+      rsLoader.startBatch();
     }
 
     @Override
     public void eval() {
-      if (in.isSet == 0) {
-        // Return empty map
-        org.apache.drill.exec.vector.complex.writer.BaseWriter.MapWriter 
mapWriter = writer.rootAsMap();
-        mapWriter.start();
-        mapWriter.end();
-        return;
-      }
-
-      try {
-        jsonReader.setSource(in.start, in.end, in.buffer);
-        jsonReader.write(writer);
-        buffer = jsonReader.getWorkBuf();
-      } catch (Exception e) {
-        throw new 
org.apache.drill.common.exceptions.DrillRuntimeException("Error while 
converting from JSON. ", e);
-      }
+      jsonLoader = 
org.apache.drill.exec.expr.fn.impl.conv.JsonConverterUtils.convertJson(
+        rsLoader, jsonLoader, options, streamIter, in.isSet, in.start, in.end, 
in.buffer, false, false, false);
     }
   }
 
@@ -379,43 +312,32 @@ public class JsonConvertFrom {
     @Param
     BitHolder readNumbersAsDoubleHolder;
 
+    @Output
+    ComplexWriter writer;
+
     @Inject
-    DrillBuf buffer;
+    OptionManager options;
+
+    @Inject
+    ResultSetLoader rsLoader;
 
     @Workspace
-    org.apache.drill.exec.vector.complex.fn.JsonReader jsonReader;
+    org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator 
streamIter;
 
-    @Output ComplexWriter writer;
+    @Workspace
+    org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl jsonLoader;
 
     @Override
     public void setup() {
-      boolean allTextMode = allTextModeHolder.value == 1;
-      boolean readNumbersAsDouble = readNumbersAsDoubleHolder.value == 1;
-
-      jsonReader = new 
org.apache.drill.exec.vector.complex.fn.JsonReader.Builder(buffer)
-          .defaultSchemaPathColumns()
-          .allTextMode(allTextMode)
-          .readNumbersAsDouble(readNumbersAsDouble)
-          .build();
+      streamIter = new 
org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator();
+      rsLoader.startBatch();
     }
 
     @Override
     public void eval() {
-      if (in.isSet == 0) {
-        // Return empty map
-        org.apache.drill.exec.vector.complex.writer.BaseWriter.MapWriter 
mapWriter = writer.rootAsMap();
-        mapWriter.start();
-        mapWriter.end();
-        return;
-      }
-
-      try {
-        jsonReader.setSource(in.start, in.end, in.buffer);
-        jsonReader.write(writer);
-        buffer = jsonReader.getWorkBuf();
-      } catch (Exception e) {
-        throw new 
org.apache.drill.common.exceptions.DrillRuntimeException("Error while 
converting from JSON. ", e);
-      }
+      jsonLoader = 
org.apache.drill.exec.expr.fn.impl.conv.JsonConverterUtils.convertJson(
+        rsLoader, jsonLoader, options, streamIter, in.isSet, in.start, in.end, 
in.buffer, true,
+        allTextModeHolder.value == 1, readNumbersAsDoubleHolder.value == 1);
     }
   }
 
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/conv/JsonConverterUtils.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/conv/JsonConverterUtils.java
new file mode 100644
index 0000000000..fbcaaf6fd1
--- /dev/null
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/conv/JsonConverterUtils.java
@@ -0,0 +1,181 @@
+/*
+ * 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.drill.exec.expr.fn.impl.conv;
+
+
+import io.netty.buffer.DrillBuf;
+import org.apache.drill.common.exceptions.UserException;
+import org.apache.drill.exec.physical.resultSet.ResultSetLoader;
+import org.apache.drill.exec.physical.resultSet.RowSetLoader;
+import org.apache.drill.exec.server.options.OptionManager;
+import org.apache.drill.exec.store.easy.json.loader.ClosingStreamIterator;
+import org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl;
+import 
org.apache.drill.exec.store.easy.json.loader.JsonLoaderImpl.JsonLoaderBuilder;
+import org.apache.drill.exec.store.easy.json.loader.JsonLoaderOptions;
+import org.apache.drill.exec.vector.complex.fn.DrillBufInputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JsonConverterUtils {
+
+  private static final Logger logger = 
LoggerFactory.getLogger(JsonConverterUtils.class);
+
+  /**
+   * Field name used to wrap the input value so that the record-oriented JSON
+   * loader can read top-level scalars and arrays (not just objects) uniformly.
+   * The single resulting column carries the converted value and is recognised
+   * and unwrapped by {@code ProjectRecordBatch} (which transfers it directly 
to
+   * the output column instead of wrapping the loader's columns in a map).
+   */
+  public static final String WRAP_FIELD = "drill_json_value_wrapper";
+
+  private static final byte[] WRAP_PREFIX =
+      ("{\"" + WRAP_FIELD + "\":").getBytes(StandardCharsets.UTF_8);
+  private static final byte[] WRAP_SUFFIX = 
"}".getBytes(StandardCharsets.UTF_8);
+
+  private JsonConverterUtils() {
+  }
+
+  /**
+   * Creates a {@link JsonLoaderImpl} for use in JSON conversion UDFs, using 
the
+   * system JSON options from the {@link OptionManager}.
+   *
+   * @param rsLoader The {@link ResultSetLoader} used in the UDF
+   * @param options The {@link OptionManager} used in the UDF.  This is used 
to extract the global JSON options
+   * @param stream An input stream containing the input JSON data
+   * @return A {@link JsonLoaderImpl} for use in the UDF.
+   */
+  public static JsonLoaderImpl createJsonLoader(ResultSetLoader rsLoader,
+                                                OptionManager options,
+                                                ClosingStreamIterator stream) {
+    JsonLoaderBuilder jsonLoaderBuilder = new JsonLoaderBuilder()
+        .resultSetLoader(rsLoader)
+        .standardOptions(options)
+        .fromStream(() -> stream);
+
+    return (JsonLoaderImpl) jsonLoaderBuilder.build();
+  }
+
+  /**
+   * Creates a {@link JsonLoaderImpl} for use in JSON conversion UDFs, 
overriding the
+   * {@code allTextMode} and {@code readNumbersAsDouble} options with the 
values supplied
+   * as function arguments.  Remaining options are taken from the system JSON 
options.
+   *
+   * @param rsLoader The {@link ResultSetLoader} used in the UDF
+   * @param options The {@link OptionManager} used in the UDF.  This is used 
to extract the global JSON options
+   * @param stream An input stream containing the input JSON data
+   * @param allTextMode Whether to read all scalars as text
+   * @param readNumbersAsDouble Whether to read all numbers as doubles
+   * @return A {@link JsonLoaderImpl} for use in the UDF.
+   */
+  public static JsonLoaderImpl createJsonLoader(ResultSetLoader rsLoader,
+                                                OptionManager options,
+                                                ClosingStreamIterator stream,
+                                                boolean allTextMode,
+                                                boolean readNumbersAsDouble) {
+    JsonLoaderOptions jsonOptions = new JsonLoaderOptions(options);
+    jsonOptions.allTextMode = allTextMode;
+    jsonOptions.readNumbersAsDouble = readNumbersAsDouble;
+
+    JsonLoaderBuilder jsonLoaderBuilder = new JsonLoaderBuilder()
+        .resultSetLoader(rsLoader)
+        .options(jsonOptions)
+        .fromStream(() -> stream);
+
+    return (JsonLoaderImpl) jsonLoaderBuilder.build();
+  }
+
+  /**
+   * Converts a single JSON value (one row of UDF input) into the result set 
loader.
+   *
+   * <p>Exactly one row is always written to the loader -- even for null/empty
+   * input -- so that the loader's row count stays aligned with the surrounding
+   * project batch. The value is wrapped in a single-field object so that
+   * top-level scalars and arrays parse the same way as objects.</p>
+   *
+   * @param rsLoader the result set loader injected into the UDF
+   * @param jsonLoader the lazily created JSON loader, or {@code null} on the 
first call
+   * @param options the system JSON options
+   * @param stream the streaming iterator bound to the JSON loader
+   * @param isSet 0 if the input value is null, 1 otherwise
+   * @param start start offset of the value in {@code buffer}
+   * @param end end offset of the value in {@code buffer}
+   * @param buffer buffer holding the input value
+   * @param useArgs whether to honour the explicit 
allTextMode/readNumbersAsDouble arguments
+   * @param allTextMode explicit all-text-mode argument (used only when {@code 
useArgs})
+   * @param readNumbersAsDouble explicit read-numbers-as-double argument (used 
only when {@code useArgs})
+   * @return the JSON loader (created on first use), to be cached by the caller
+   */
+  public static JsonLoaderImpl convertJson(ResultSetLoader rsLoader,
+                                           JsonLoaderImpl jsonLoader,
+                                           OptionManager options,
+                                           ClosingStreamIterator stream,
+                                           int isSet, int start, int end, 
DrillBuf buffer,
+                                           boolean useArgs,
+                                           boolean allTextMode,
+                                           boolean readNumbersAsDouble) {
+    RowSetLoader rowWriter = rsLoader.writer();
+    rowWriter.start();
+    // For null or empty input emit an (unset) row to keep the row count 
aligned.
+    if (isSet == 0 || start == end) {
+      rowWriter.save();
+      return jsonLoader;
+    }
+
+    try {
+      stream.setValue(getWrappedJsonStream(start, end, buffer));
+      if (jsonLoader == null) {
+        jsonLoader = useArgs
+            ? createJsonLoader(rsLoader, options, stream, allTextMode, 
readNumbersAsDouble)
+            : createJsonLoader(rsLoader, options, stream);
+      }
+      // next() reads the single wrapped record; always save the row so the
+      // count stays aligned even for an empty document.
+      jsonLoader.parser().next();
+      rowWriter.save();
+    } catch (Exception e) {
+      throw UserException.dataReadError(e)
+          .message("Error while reading JSON. ")
+          .addContext(e.getMessage())
+          .build(logger);
+    }
+    return jsonLoader;
+  }
+
+  /**
+   * Wraps the raw input value in a single-field JSON object
+   * ({@code {"drill_json_value_wrapper": <value>}}, see {@link #WRAP_FIELD}) 
so that
+   * the record-oriented JSON loader accepts top-level scalars and arrays.
+   */
+  private static InputStream getWrappedJsonStream(int start, int end, DrillBuf 
buffer) {
+    InputStream value = DrillBufInputStream.getStream(start, end, buffer);
+    return new SequenceInputStream(Collections.enumeration(Arrays.asList(
+        new ByteArrayInputStream(WRAP_PREFIX),
+        value,
+        new ByteArrayInputStream(WRAP_SUFFIX))));
+  }
+}
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/project/ProjectBatchBuilder.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/project/ProjectBatchBuilder.java
index 056f4c09a7..96bfb378ab 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/project/ProjectBatchBuilder.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/project/ProjectBatchBuilder.java
@@ -95,9 +95,10 @@ public class ProjectBatchBuilder implements 
ProjectionMaterializer.BatchBuilder
 
   @Override
   public void addComplexField(FieldReference ref) {
-    if (projectBatch.rsLoader == null) {
-      initComplexWriters();
-    }
+    // The result set loaders are not registered until the generated projector
+    // setup runs (after this point), so the complex-writer list is always
+    // (re)initialised here; it is simply left unused when loaders are present.
+    initComplexWriters();
     if (projectBatch.complexFieldReferencesList == null) {
       projectBatch.complexFieldReferencesList = Lists.newArrayList();
     } else {
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/project/ProjectRecordBatch.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/project/ProjectRecordBatch.java
index a23227a275..0b373cac77 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/project/ProjectRecordBatch.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/project/ProjectRecordBatch.java
@@ -24,12 +24,14 @@ import org.apache.drill.common.types.TypeProtos;
 import org.apache.drill.common.types.Types;
 import org.apache.drill.exec.ExecConstants;
 import org.apache.drill.exec.exception.SchemaChangeException;
+import org.apache.drill.exec.expr.fn.impl.conv.JsonConverterUtils;
 import org.apache.drill.exec.ops.FragmentContext;
 import org.apache.drill.exec.physical.config.Project;
 import org.apache.drill.exec.physical.resultSet.ResultSetLoader;
 import org.apache.drill.exec.record.AbstractSingleRecordBatch;
 import org.apache.drill.exec.record.BatchSchema;
 import org.apache.drill.exec.record.BatchSchema.SelectionVectorMode;
+import org.apache.drill.exec.record.MaterializedField;
 import org.apache.drill.exec.record.RecordBatch;
 import org.apache.drill.exec.record.SimpleRecordBatch;
 import org.apache.drill.exec.record.VectorContainer;
@@ -54,7 +56,14 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
   protected List<ValueVector> allocationVectors;
   @Deprecated // use new writer rsLoader
   protected List<ComplexWriter> complexWriters;
-  protected ResultSetLoader rsLoader;
+  // One result set loader per complex-writer (EVF) function in the project 
list.
+  // rsLoaderRefs holds each loader's output column name, captured at codegen, 
so
+  // the harvested column lands in the slot reserved for that function.
+  protected List<ResultSetLoader> rsLoaders;
+  private List<String> rsLoaderRefs;
+  // True once the loaders have been harvested and must be re-started before 
the
+  // next output batch is written.
+  private boolean loadersNeedStart;
   protected List<FieldReference> complexFieldReferencesList;
   protected ProjectMemoryManager memoryManager;
   private Projector projector;
@@ -111,7 +120,7 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
     memoryManager.update();
 
     if (first && incomingRecordCount == 0) {
-      if (!CollectionUtils.isEmpty(complexWriters) || rsLoader != null ) {
+      if (!CollectionUtils.isEmpty(complexWriters) || 
!CollectionUtils.isEmpty(rsLoaders)) {
         IterOutcome next = null;
         while (incomingRecordCount == 0) {
           if (getLastKnownOutcome() == EMIT) {
@@ -146,7 +155,7 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
       }
     }
 
-    if ((!CollectionUtils.isEmpty(complexWriters) || rsLoader != null) && 
getLastKnownOutcome() == EMIT) {
+    if ((!CollectionUtils.isEmpty(complexWriters) || 
!CollectionUtils.isEmpty(rsLoaders)) && getLastKnownOutcome() == EMIT) {
       throw UserException.unsupportedError()
           .message("Currently functions producing complex types as output are 
not " +
             "supported in project list for subquery between LATERAL and 
UNNEST. Please re-write the query using this " +
@@ -162,6 +171,7 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
                  memoryManager.getOutputRowCount(), incomingRecordCount, 
incoming, this);
 
     doAlloc(maxOuputRecordCount);
+    startLoaderBatchIfNeeded();
     long projectStartTime = System.currentTimeMillis();
     int outputRecords = projector.projectRecords(incoming, 0, 
maxOuputRecordCount, 0);
     long projectEndTime = System.currentTimeMillis();
@@ -178,14 +188,8 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
     }
     // In case of complex writer expression, vectors would be added to batch 
run-time.
     // We have to re-build the schema.
-    if (rsLoader != null) {
-      MapVector map = 
container.addOrGet(container.getLast().getField().getName(), 
Types.required(TypeProtos.MinorType.MAP), MapVector.class);
-      map.setMapValueCount(recordCount);
-      for (VectorWrapper<?> vectorWrapper : rsLoader.harvest()) {
-        ValueVector valueVector = vectorWrapper.getValueVector();
-        map.putChild(valueVector.getField().getName(), valueVector);
-      }
-      container.buildSchema(SelectionVectorMode.NONE);
+    if (!CollectionUtils.isEmpty(rsLoaders)) {
+      harvestLoaders();
     } else if (!CollectionUtils.isEmpty(complexWriters)) {
       container.buildSchema(SelectionVectorMode.NONE);
     }
@@ -202,6 +206,7 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
     assert memoryManager.incomingBatch() == incoming;
     int recordsToProcess = Math.min(remainingRecordCount, 
memoryManager.getOutputRowCount());
     doAlloc(recordsToProcess);
+    startLoaderBatchIfNeeded();
 
     logger.trace("handleRemainder: remaining RC {}, toProcess {}, remainder 
index {}, incoming {}, Project {}",
                  remainingRecordCount, recordsToProcess, remainderIndex, 
incoming, this);
@@ -225,7 +230,9 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
     }
     // In case of complex writer expression, vectors would be added to batch 
run-time.
     // We have to re-build the schema.
-    if (!CollectionUtils.isEmpty(complexWriters) || rsLoader != null) {
+    if (!CollectionUtils.isEmpty(rsLoaders)) {
+      harvestLoaders();
+    } else if (!CollectionUtils.isEmpty(complexWriters)) {
       container.buildSchema(SelectionVectorMode.NONE);
     }
 
@@ -239,8 +246,65 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
     complexWriters.add(writer);
   }
 
-  public void addLoader(ResultSetLoader loader) {
-    rsLoader = loader;
+  public void addLoader(ResultSetLoader loader, String refName) {
+    if (rsLoaders == null) {
+      rsLoaders = new ArrayList<>();
+      rsLoaderRefs = new ArrayList<>();
+    }
+    rsLoaders.add(loader);
+    rsLoaderRefs.add(refName);
+  }
+
+  /**
+   * Re-starts each result set loader batch when the previous batch has already
+   * been harvested. The loaders start their first batch from the generated UDF
+   * setup; this drives every subsequent output batch.
+   */
+  private void startLoaderBatchIfNeeded() {
+    if (loadersNeedStart && !CollectionUtils.isEmpty(rsLoaders)) {
+      for (ResultSetLoader loader : rsLoaders) {
+        loader.startBatch();
+      }
+      loadersNeedStart = false;
+    }
+  }
+
+  /**
+   * Harvests each complex-writer function's result set loader and moves its
+   * single (wrapped) output column into the container slot reserved for that
+   * function (see {@link ProjectBatchBuilder#addComplexField}).
+   */
+  private void harvestLoaders() {
+    for (int i = 0; i < rsLoaders.size(); i++) {
+      String refName = rsLoaderRefs.get(i);
+      VectorContainer harvested = rsLoaders.get(i).harvest();
+      List<ValueVector> columns = new ArrayList<>();
+      for (VectorWrapper<?> w : harvested) {
+        columns.add(w.getValueVector());
+      }
+
+      if (columns.size() == 1
+          && 
JsonConverterUtils.WRAP_FIELD.equals(columns.get(0).getField().getName())) {
+        // convert_fromJSON wraps its value in a single marker column. Unwrap 
it
+        // so the output preserves the value's own type (scalar, array or map).
+        ValueVector src = columns.get(0);
+        ValueVector dst = container.addOrGet(
+            MaterializedField.create(refName, src.getField().getType()), 
callBack);
+        src.makeTransferPair(dst).transfer();
+      } else {
+        // The loader produced the JSON object's fields directly (e.g. the HTTP
+        // UDFs, or an empty/all-null document). Wrap them in a map named for 
the
+        // output column.
+        MapVector map = container.addOrGet(refName,
+            Types.required(TypeProtos.MinorType.MAP), MapVector.class);
+        map.setMapValueCount(recordCount);
+        for (ValueVector src : columns) {
+          map.putChild(src.getField().getName(), src);
+        }
+      }
+    }
+    loadersNeedStart = true;
+    container.buildSchema(SelectionVectorMode.NONE);
   }
 
   private void doAlloc(int recordCount) {
@@ -250,7 +314,7 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
     }
 
     // Allocate vv for complexWriters.
-    if (complexWriters != null) {
+    if (!CollectionUtils.isEmpty(complexWriters)) {
       for (ComplexWriter writer : complexWriters) {
         writer.allocate();
       }
@@ -270,12 +334,12 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
     // the transfer pairs or vector copies.
     container.setRecordCount(count);
 
-    if (complexWriters != null) {
+    // Result set loaders need nothing here: their row count follows the rows 
the
+    // generated function wrote, and the batch is harvested right after this 
call.
+    if (!CollectionUtils.isEmpty(complexWriters)) {
       for (ComplexWriter writer : complexWriters) {
         writer.setValueCount(count);
       }
-    } else if (rsLoader != null) {
-      rsLoader.setTargetRowCount(count);
     }
   }
 
@@ -359,7 +423,7 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
   protected IterOutcome getFinalOutcome(boolean hasMoreRecordInBoundary) {
     // In a case of complex writers vectors are added at runtime, so the schema
     // may change (e.g. when a batch contains new column(s) not present in 
previous batches)
-    if (!CollectionUtils.isEmpty(complexWriters) || rsLoader != null) {
+    if (!CollectionUtils.isEmpty(complexWriters) || 
!CollectionUtils.isEmpty(rsLoaders)) {
       return IterOutcome.OK_NEW_SCHEMA;
     }
     return super.getFinalOutcome(hasMoreRecordInBoundary);
@@ -375,9 +439,16 @@ public class ProjectRecordBatch extends 
AbstractSingleRecordBatch<Project> {
     }
     allocationVectors = new ArrayList<>();
 
-    if (rsLoader != null) {
+    if (!CollectionUtils.isEmpty(rsLoaders)) {
       container.clear();
-      rsLoader.close();
+      for (ResultSetLoader loader : rsLoaders) {
+        loader.close();
+      }
+      // The generated projector setup repopulates these (one loader per
+      // complex-writer function) on the rebuild that follows.
+      rsLoaders = null;
+      rsLoaderRefs = null;
+      loadersNeedStart = false;
     } else if (!CollectionUtils.isEmpty(complexWriters)) {
       container.clear();
     } else {
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonConversionUDF.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonConversionUDF.java
index d6fa857a39..4501e5f7fc 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonConversionUDF.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonConversionUDF.java
@@ -21,6 +21,7 @@ package org.apache.drill.exec.store.json;
 import org.apache.commons.io.FileUtils;
 import org.apache.drill.common.expression.SchemaPath;
 import org.apache.drill.exec.ExecConstants;
+import org.apache.drill.exec.physical.resultSet.ResultSetLoader;
 import org.apache.drill.exec.record.RecordBatchLoader;
 import org.apache.drill.exec.record.VectorWrapper;
 import org.apache.drill.exec.rpc.user.QueryDataBatch;
@@ -220,4 +221,30 @@ public class TestJsonConversionUDF extends BaseTestQuery {
     resetSessionOption(ExecConstants.JSON_ALL_TEXT_MODE);
     resetSessionOption(ExecConstants.JSON_READ_NUMBERS_AS_DOUBLE);
   }
+
+  @Test
+  public void testConvertFromJsonBatchLargerThanLoaderRowLimit() throws 
Exception {
+    // More rows than the result set loader's default row count limit, so that 
a
+    // single project batch outgrows the loader's initial target row count.
+    int rowCount = ResultSetLoader.DEFAULT_ROW_COUNT * 2 + 17;
+    String table = "large_json_input.csv";
+    File file = new File(dirTestWatcher.getRootDir(), table);
+    StringBuilder csv = new StringBuilder();
+    for (int i = 0; i < rowCount; i++) {
+      csv.append("col_").append(i).append(", 
{\"id\":").append(i).append("}\n");
+    }
+    try {
+      FileUtils.writeStringToFile(file, csv.toString(), 
Charset.defaultCharset());
+      String sql = String.format(
+          "SELECT COUNT(*) AS cnt FROM (SELECT convert_fromJSON(columns[1]) AS 
col FROM dfs.`%s`)", table);
+      testBuilder()
+          .sqlQuery(sql)
+          .unOrdered()
+          .baselineColumns("cnt")
+          .baselineValues((long) rowCount)
+          .go();
+    } finally {
+      FileUtils.deleteQuietly(file);
+    }
+  }
 }
diff --git a/result-cache/40658b21-8d70-4eef-ba2f-52881392a6a8/data.json 
b/result-cache/40658b21-8d70-4eef-ba2f-52881392a6a8/data.json
new file mode 100644
index 0000000000..387662532d
--- /dev/null
+++ b/result-cache/40658b21-8d70-4eef-ba2f-52881392a6a8/data.json
@@ -0,0 +1 @@
+[{"state":"NY","customer_count":"8542","avg_orders_per_customer":"24.983259189885274"},{"state":"CA","customer_count":"387","avg_orders_per_customer":"0.29457364341085274"},{"state":"TX","customer_count":"246","avg_orders_per_customer":"0.26422764227642276"},{"state":"FL","customer_count":"203","avg_orders_per_customer":"0.22167487684729065"},{"state":"IL","customer_count":"114","avg_orders_per_customer":"0.2543859649122807"},{"state":"PA","customer_count":"108","avg_orders_per_customer"
 [...]
\ No newline at end of file
diff --git a/result-cache/40658b21-8d70-4eef-ba2f-52881392a6a8/meta.json 
b/result-cache/40658b21-8d70-4eef-ba2f-52881392a6a8/meta.json
new file mode 100644
index 0000000000..630a4e3b1f
--- /dev/null
+++ b/result-cache/40658b21-8d70-4eef-ba2f-52881392a6a8/meta.json
@@ -0,0 +1,15 @@
+{
+  "cacheId" : "40658b21-8d70-4eef-ba2f-52881392a6a8",
+  "queryId" : "157b9289-9c18-477b-bd16-b758d2ad9787",
+  "sqlHash" : 
"1ffac56b73e4af6792d8018e2a36135137665a4508624aa7141849e45f22d5b8",
+  "sql" : "SELECT\n    c.`state`,\n    COUNT(DISTINCT c.`customerid`) AS 
`customer_count`,\n    CAST(COUNT(o.`orderid`) AS DOUBLE) / COUNT(DISTINCT 
c.`customerid`) AS `avg_orders_per_customer`\nFROM `mysql.store`.`customers` AS 
c\nLEFT JOIN `mysql.store`.`orders` AS o\n    ON c.`customerid` = 
o.`customerid`\nGROUP BY c.`state`\nORDER BY `customer_count` DESC",
+  "defaultSchema" : "",
+  "userName" : "anonymous",
+  "queryState" : "COMPLETED",
+  "columns" : [ "state", "customer_count", "avg_orders_per_customer" ],
+  "metadata" : [ "VARCHAR(2, 0)", "BIGINT(21, 0)", "FLOAT8(23, 31)" ],
+  "totalRows" : 52,
+  "sizeBytes" : 4208,
+  "cachedAt" : 1787063670634,
+  "lastAccessedAt" : 1787063670634
+}
\ No newline at end of file
diff --git a/result-cache/4bdbeab0-2721-4be8-b92e-2c61f2f8d30e/data.json 
b/result-cache/4bdbeab0-2721-4be8-b92e-2c61f2f8d30e/data.json
new file mode 100644
index 0000000000..e5525f7c60
--- /dev/null
+++ b/result-cache/4bdbeab0-2721-4be8-b92e-2c61f2f8d30e/data.json
@@ -0,0 +1 @@
+[{"state":"NY","customer_count":"8542"},{"state":"CA","customer_count":"387"},{"state":"TX","customer_count":"246"},{"state":"FL","customer_count":"203"},{"state":"IL","customer_count":"114"},{"state":"PA","customer_count":"108"},{"state":"OH","customer_count":"103"},{"state":"AZ","customer_count":"81"},{"state":"VA","customer_count":"78"},{"state":"GA","customer_count":"74"},{"state":"WA","customer_count":"69"},{"state":"MI","customer_count":"68"},{"state":"NC","customer_count":"58"},{"
 [...]
\ No newline at end of file
diff --git a/result-cache/4bdbeab0-2721-4be8-b92e-2c61f2f8d30e/meta.json 
b/result-cache/4bdbeab0-2721-4be8-b92e-2c61f2f8d30e/meta.json
new file mode 100644
index 0000000000..e98d6cff74
--- /dev/null
+++ b/result-cache/4bdbeab0-2721-4be8-b92e-2c61f2f8d30e/meta.json
@@ -0,0 +1,15 @@
+{
+  "cacheId" : "4bdbeab0-2721-4be8-b92e-2c61f2f8d30e",
+  "queryId" : "157b92ce-9cd8-9ccc-57cb-c910ba8bc627",
+  "sqlHash" : 
"ceb1b41179fe1c884c794b4cf71797098eaa8461c1ac82c8684402d1e160a403",
+  "sql" : "SELECT `state`, COUNT(*) AS `customer_count`\nFROM 
`mysql.store`.`customers`\nGROUP BY `state`\nORDER BY `customer_count` DESC",
+  "defaultSchema" : "",
+  "userName" : "anonymous",
+  "queryState" : "COMPLETED",
+  "columns" : [ "state", "customer_count" ],
+  "metadata" : [ "VARCHAR(2, 0)", "BIGINT(21, 0)" ],
+  "totalRows" : 52,
+  "sizeBytes" : 1922,
+  "cachedAt" : 1787063600807,
+  "lastAccessedAt" : 1787063600807
+}
\ No newline at end of file

Reply via email to