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

xiangfu0 pushed a commit to branch agent/experimental-fory-json-functions
in repository https://gitbox.apache.org/repos/asf/pinot.git

commit 62c2db4b670153709edc03eb98bf70fcb53b5880
Author: Xiang Fu <[email protected]>
AuthorDate: Wed Aug 12 12:50:01 2026 -0700

    Benchmark Fory JSON function counterparts
---
 .../common/function/FastJsonPathExtractorTest.java |   1 +
 .../JsonExtractScalarTransformFunction.java        |   6 +-
 .../core/data/function/JsonFunctionsTest.java      |   9 +
 .../perf/BenchmarkJsonExtractScalarQuery.java      | 198 +++++++++++++++++----
 .../pinot/perf/BenchmarkJsonPathExtraction.java    | 141 +++++++++++++--
 5 files changed, 302 insertions(+), 53 deletions(-)

diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/function/FastJsonPathExtractorTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/common/function/FastJsonPathExtractorTest.java
index 3212b705d1b..f4677acb672 100644
--- 
a/pinot-common/src/test/java/org/apache/pinot/common/function/FastJsonPathExtractorTest.java
+++ 
b/pinot-common/src/test/java/org/apache/pinot/common/function/FastJsonPathExtractorTest.java
@@ -397,6 +397,7 @@ public class FastJsonPathExtractorTest {
     assertEquals(invoke("jsonPathStringFast", json, "$.user.country", 
"DEFAULT"), "US");
     assertEquals(invoke("jsonPathStringFirstMatch", json, "$.user.country", 
"DEFAULT"), "US");
     assertEquals(invoke("jsonPathStringFast", json, "$.missing", "DEFAULT"), 
"DEFAULT");
+    assertEquals(invoke("jsonPathLongFast", json, "$.user.age", -7L), 41L);
     assertEquals(invoke("jsonPathLongFirstMatch", json, "$.user.age", -7L), 
41L);
     assertEquals(invoke("jsonPathDoubleFast", json, "$.user.score", -7.5d), 
9.5d);
     /// A complex path must still resolve through the function by falling back 
to Jayway, i.e. produce exactly
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractScalarTransformFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractScalarTransformFunction.java
index 43df67fa1f5..a16e73990d0 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractScalarTransformFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractScalarTransformFunction.java
@@ -75,9 +75,9 @@ import org.roaringbitmap.RoaringBitmap;
 /// only for well-formed, duplicate-free JSON. `Fast` scans the full root 
value and retains Jayway's last-key-wins
 /// and malformed-document behavior; see [FastJsonPathExtractor] for one 
documented unaddressed-value edge case.
 /// `jsonExtractScalarFory` is experimental and must be selected explicitly. 
It accelerates simple paths over
-/// `STRING` input for scalar result types other than `STRING` and 
`BIG_DECIMAL`. `BYTES` input, complex paths,
-/// containers / array result types, precision-sensitive results, deeply 
nested documents, and Fory failures use
-/// Jayway. Its name, supported envelope, and implementation can change while 
the integration is evaluated.
+/// `STRING` input for scalar result types other than `STRING`, `JSON`, and 
`BIG_DECIMAL`. `BYTES` input, complex
+/// paths, containers / array result types, precision-sensitive results, 
deeply nested documents, and Fory failures
+/// use Jayway. Its name, supported envelope, and implementation can change 
while the integration is evaluated.
 ///
 /// **Arguments:**
 /// - `jsonField` — single-value `STRING` or `BYTES` column / transform 
expression containing JSON.
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/data/function/JsonFunctionsTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/JsonFunctionsTest.java
index df97c4b2926..bbaed22658a 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/data/function/JsonFunctionsTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/JsonFunctionsTest.java
@@ -147,6 +147,15 @@ public class JsonFunctionsTest {
     inputs.add(new Object[]{
         "json_path_double_fory(json, '$.ratio', -1.0)", 
Lists.newArrayList("json"), row12, 1.25
     });
+    inputs.add(new Object[]{
+        "json_path_string_fast(json, '$.text', 'DEFAULT')", 
Lists.newArrayList("json"), row12, "value"
+    });
+    inputs.add(new Object[]{
+        "json_path_long_fast(json, '$.count', -1)", 
Lists.newArrayList("json"), row12, 10L
+    });
+    inputs.add(new Object[]{
+        "json_path_double_fast(json, '$.ratio', -1.0)", 
Lists.newArrayList("json"), row12, 1.25
+    });
     return inputs.toArray(new Object[0][]);
   }
 
diff --git 
a/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkJsonExtractScalarQuery.java
 
b/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkJsonExtractScalarQuery.java
index 47fb9787b5f..a89f77aedca 100644
--- 
a/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkJsonExtractScalarQuery.java
+++ 
b/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkJsonExtractScalarQuery.java
@@ -21,7 +21,10 @@ package org.apache.pinot.perf;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.TimeUnit;
+import org.apache.pinot.common.function.ForyJsonPathExtractor;
+import org.apache.pinot.common.function.SimpleJsonPath;
 import org.apache.pinot.common.request.context.ExpressionContext;
 import org.apache.pinot.common.request.context.LiteralContext;
 import org.apache.pinot.core.common.BlockValSet;
@@ -53,6 +56,9 @@ import org.openjdk.jmh.runner.options.OptionsBuilder;
 /// Measures the actual query-side `jsonExtractScalar*` ValueBlock loop. Each 
invocation processes a 128-row block;
 /// JMH normalizes throughput and allocation to one row via 
[OperationsPerInvocation]. Input projection is represented
 /// by a pre-materialized String array so the comparison isolates JSON 
extraction, type coercion, and result writing.
+/// Result dispatch is selected once during setup, outside the measured 
per-row loop. The type-specific JSON literals
+/// have equal encoded lengths and occupy the same early/late field locations 
so STRING, LONG, and DOUBLE comparisons
+/// do not accidentally measure different document layouts.
 @BenchmarkMode(Mode.Throughput)
 @OutputTimeUnit(TimeUnit.SECONDS)
 @Fork(1)
@@ -63,8 +69,8 @@ public class BenchmarkJsonExtractScalarQuery {
   private static final int BLOCK_ROWS = 128;
   private static final TransformResultMetadata STRING_METADATA =
       new TransformResultMetadata(DataType.STRING, true, false);
-  private static final String BASE_JSON = "{"
-      + "\"earlyMetric\":17,"
+  private static final String BASE_JSON_FORMAT = "{"
+      + "\"earlyValue\":%s,"
       + 
"\"user\":{\"id\":\"u-19283\",\"country\":\"US\",\"tier\":\"gold\",\"age\":41},"
       + 
"\"event\":{\"name\":\"checkout\",\"cart\":[{\"sku\":\"A1\",\"qty\":2,\"price\":19.99},"
       + 
"{\"sku\":\"B7\",\"qty\":1,\"price\":129.0},{\"sku\":\"C3\",\"qty\":4,\"price\":3.5}],"
@@ -75,7 +81,8 @@ public class BenchmarkJsonExtractScalarQuery {
       + "\"coordinates\":[-122.4194,37.7749]},"
       + 
"\"attributes\":{\"campaign\":\"summer-sale\",\"referrer\":\"search\",\"experiment\":\"checkout-v2\"},"
       + "\"flags\":[\"returning\",\"subscribed\",\"beta\"],"
-      + "\"lateMetric\":19} ";
+      + "\"lateValue\":%s} ";
+  private static final String LATE_FIELD_MARKER = "\"lateValue\":";
 
   @Param({"early", "late"})
   private String _fieldPosition;
@@ -83,6 +90,15 @@ public class BenchmarkJsonExtractScalarQuery {
   @Param({"700", "8192", "65536"})
   private int _documentBytes;
 
+  /// STRING is an intentional Jayway fallback for precision-safe coercion. 
Add `-p _resultType=STRING` explicitly to
+  /// characterize that public-function fallback; default trials cover only 
actual Fory streaming.
+  @Param({"LONG", "DOUBLE"})
+  private DataType _resultType;
+
+  /// Use `-p _pathResult=missing` to measure explicit defaults without 
multiplying the default suite.
+  @Param({"hit"})
+  private String _pathResult;
+
   private ValueBlock _valueBlock;
   private JsonExtractScalarTransformFunction _jayway;
   private JsonExtractScalarTransformFunction _fast;
@@ -91,67 +107,181 @@ public class BenchmarkJsonExtractScalarQuery {
 
   @Setup
   public void setUp() {
-    String json = buildJson(_documentBytes);
+    if (_resultType != DataType.STRING && 
!ForyJsonPathExtractor.isAvailable()) {
+      throw new IllegalStateException("Fory JSON is unavailable; refusing to 
publish fallback results as Fory");
+    }
+    String json = buildJson(_documentBytes, _resultType);
     String[] jsonRows = new String[BLOCK_ROWS];
     Arrays.fill(jsonRows, json);
     TransformFunction input = new StringArrayTransformFunction(jsonRows);
-    String path = "early".equals(_fieldPosition) ? "$.earlyMetric" : 
"$.lateMetric";
-    List<TransformFunction> arguments = List.of(input, literal(path), 
literal("LONG"));
+    boolean early = "early".equals(_fieldPosition);
+    boolean hit = "hit".equals(_pathResult);
+    String path = "$." + (early ? "early" : "late") + (hit ? "Value" : 
"Ghost");
+    Object defaultValue = defaultValue(_resultType);
+    List<TransformFunction> arguments = List.of(input, 
literal(DataType.STRING, path),
+        literal(DataType.STRING, _resultType.name()), literal(_resultType, 
defaultValue));
 
     _valueBlock = new FixedValueBlock(BLOCK_ROWS);
-    _jayway = new JsonExtractScalarTransformFunction();
-    _fast = new JsonExtractScalarTransformFunction.Fast();
-    _firstMatch = new JsonExtractScalarTransformFunction.FirstMatch();
-    _fory = new JsonExtractScalarTransformFunction.Fory();
-    for (JsonExtractScalarTransformFunction function : List.of(_jayway, _fast, 
_firstMatch, _fory)) {
-      function.init(arguments, Map.<String, ColumnContext>of(), false);
-      long expected = "early".equals(_fieldPosition) ? 17L : 19L;
-      long[] values = function.transformToLongValuesSV(_valueBlock);
-      if (values.length < BLOCK_ROWS || values[0] != expected || 
values[BLOCK_ROWS - 1] != expected) {
-        throw new IllegalStateException(function.getName() + " produced an 
unexpected query result");
+    if (_resultType != DataType.STRING) {
+      Object directForyResult = ForyJsonPathExtractor.extract(json, 
SimpleJsonPath.compile(path));
+      Object expectedForyResult = hit ? hitValue(_resultType, early) : null;
+      if (!Objects.equals(directForyResult, expectedForyResult)) {
+        throw new IllegalStateException("Direct Fory extraction produced an 
unexpected " + _resultType + " result");
       }
     }
+    _jayway = initialize(new JsonExtractScalarTransformFunction(), arguments);
+    _fast = initialize(new JsonExtractScalarTransformFunction.Fast(), 
arguments);
+    _firstMatch = initialize(new 
JsonExtractScalarTransformFunction.FirstMatch(), arguments);
+    _fory = initialize(new JsonExtractScalarTransformFunction.Fory(), 
arguments);
+
+    Object expectedRows = expectedRows(_resultType, hit ? 
hitValue(_resultType, early) : defaultValue);
+    Object jaywayRows = apply(_jayway);
+    assertResultsEqual("jsonExtractScalar", expectedRows, jaywayRows);
+    assertResultsEqual("jsonExtractScalarFast", jaywayRows, apply(_fast));
+    assertResultsEqual("jsonExtractScalarFirstMatch", jaywayRows, 
apply(_firstMatch));
+    assertResultsEqual("jsonExtractScalarFory", jaywayRows, apply(_fory));
   }
 
-  private static LiteralTransformFunction literal(String value) {
-    return new LiteralTransformFunction(new LiteralContext(DataType.STRING, 
value));
+  private JsonExtractScalarTransformFunction 
initialize(JsonExtractScalarTransformFunction function,
+      List<TransformFunction> arguments) {
+    function.init(arguments, Map.<String, ColumnContext>of(), false);
+    return function;
   }
 
-  private static String buildJson(int targetBytes) {
-    if (BASE_JSON.length() >= targetBytes) {
-      return BASE_JSON;
+  private Object apply(JsonExtractScalarTransformFunction function) {
+    switch (_resultType) {
+      case STRING:
+        return function.transformToStringValuesSV(_valueBlock);
+      case LONG:
+        return function.transformToLongValuesSV(_valueBlock);
+      case DOUBLE:
+        return function.transformToDoubleValuesSV(_valueBlock);
+      default:
+        throw new IllegalStateException("Unsupported benchmark result type: " 
+ _resultType);
     }
-    String marker = "\"lateMetric\":";
-    int markerOffset = BASE_JSON.indexOf(marker);
+  }
+
+  private static LiteralTransformFunction literal(DataType dataType, Object 
value) {
+    return new LiteralTransformFunction(new LiteralContext(dataType, value));
+  }
+
+  private static String buildJson(int targetBytes, DataType resultType) {
+    String baseJson = String.format(BASE_JSON_FORMAT, jsonLiteral(resultType, 
true), jsonLiteral(resultType, false));
+    if (baseJson.length() >= targetBytes) {
+      return baseJson;
+    }
+    int markerOffset = baseJson.indexOf(LATE_FIELD_MARKER);
     String paddingPrefix = "\"padding\":\"";
     String paddingSuffix = "\",";
-    int paddingLength = targetBytes - BASE_JSON.length() - 
paddingPrefix.length() - paddingSuffix.length();
-    return BASE_JSON.substring(0, markerOffset) + paddingPrefix + 
"x".repeat(paddingLength) + paddingSuffix
-        + BASE_JSON.substring(markerOffset);
+    int paddingLength = targetBytes - baseJson.length() - 
paddingPrefix.length() - paddingSuffix.length();
+    return baseJson.substring(0, markerOffset) + paddingPrefix + 
"x".repeat(paddingLength) + paddingSuffix
+        + baseJson.substring(markerOffset);
+  }
+
+  private static String jsonLiteral(DataType resultType, boolean early) {
+    switch (resultType) {
+      case STRING:
+        return early ? "\"S\"" : "\"T\"";
+      case LONG:
+        return early ? "170" : "190";
+      case DOUBLE:
+        return early ? "1.7" : "1.9";
+      default:
+        throw new IllegalStateException("Unsupported benchmark result type: " 
+ resultType);
+    }
+  }
+
+  private static Object hitValue(DataType resultType, boolean early) {
+    switch (resultType) {
+      case STRING:
+        return early ? "S" : "T";
+      case LONG:
+        return early ? 170L : 190L;
+      case DOUBLE:
+        return early ? 1.7d : 1.9d;
+      default:
+        throw new IllegalStateException("Unsupported benchmark result type: " 
+ resultType);
+    }
+  }
+
+  private static Object defaultValue(DataType resultType) {
+    switch (resultType) {
+      case STRING:
+        return "DEFAULT";
+      case LONG:
+        return -1L;
+      case DOUBLE:
+        return -1d;
+      default:
+        throw new IllegalStateException("Unsupported benchmark result type: " 
+ resultType);
+    }
+  }
+
+  private static Object expectedRows(DataType resultType, Object 
expectedValue) {
+    switch (resultType) {
+      case STRING:
+        String[] stringValues = new String[BLOCK_ROWS];
+        Arrays.fill(stringValues, (String) expectedValue);
+        return stringValues;
+      case LONG:
+        long[] longValues = new long[BLOCK_ROWS];
+        Arrays.fill(longValues, (Long) expectedValue);
+        return longValues;
+      case DOUBLE:
+        double[] doubleValues = new double[BLOCK_ROWS];
+        Arrays.fill(doubleValues, (Double) expectedValue);
+        return doubleValues;
+      default:
+        throw new IllegalStateException("Unsupported benchmark result type: " 
+ resultType);
+    }
+  }
+
+  private void assertResultsEqual(String functionName, Object expected, Object 
actual) {
+    boolean equal;
+    switch (_resultType) {
+      case STRING:
+        equal = Arrays.equals((String[]) expected, (String[]) actual);
+        break;
+      case LONG:
+        equal = Arrays.equals((long[]) expected, (long[]) actual);
+        break;
+      case DOUBLE:
+        equal = Arrays.equals((double[]) expected, (double[]) actual);
+        break;
+      default:
+        throw new IllegalStateException("Unsupported benchmark result type: " 
+ _resultType);
+    }
+    if (!equal) {
+      throw new IllegalStateException(functionName + " produced an unexpected 
" + _resultType + " result for "
+          + _fieldPosition + '/' + _pathResult);
+    }
   }
 
   @Benchmark
   @OperationsPerInvocation(BLOCK_ROWS)
-  public long[] queryJayway() {
-    return _jayway.transformToLongValuesSV(_valueBlock);
+  public Object queryJayway() {
+    return apply(_jayway);
   }
 
   @Benchmark
   @OperationsPerInvocation(BLOCK_ROWS)
-  public long[] queryFast() {
-    return _fast.transformToLongValuesSV(_valueBlock);
+  public Object queryFast() {
+    return apply(_fast);
   }
 
+  /// Auxiliary comparator: unlike the primary parity-preserving variants, 
FirstMatch intentionally has weaker
+  /// duplicate-key and malformed-tail semantics.
   @Benchmark
   @OperationsPerInvocation(BLOCK_ROWS)
-  public long[] queryFirstMatch() {
-    return _firstMatch.transformToLongValuesSV(_valueBlock);
+  public Object queryFirstMatch() {
+    return apply(_firstMatch);
   }
 
+  /// For STRING, this measures the production Fory function's intentional 
Jayway fallback rather than Fory parsing.
   @Benchmark
   @OperationsPerInvocation(BLOCK_ROWS)
-  public long[] queryFory() {
-    return _fory.transformToLongValuesSV(_valueBlock);
+  public Object queryFory() {
+    return apply(_fory);
   }
 
   public static void main(String[] arguments)
diff --git 
a/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkJsonPathExtraction.java
 
b/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkJsonPathExtraction.java
index 185e92635d1..2e1b8fa6d84 100644
--- 
a/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkJsonPathExtraction.java
+++ 
b/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkJsonPathExtraction.java
@@ -29,6 +29,7 @@ import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
 import java.util.concurrent.TimeUnit;
 import org.apache.fory.json.ForyJson;
 import org.apache.pinot.common.function.FastJsonPathExtractor;
+import org.apache.pinot.common.function.ForyJsonPathExtractor;
 import org.apache.pinot.common.function.SimpleJsonPath;
 import org.apache.pinot.common.function.scalar.JsonFunctions;
 import org.openjdk.jmh.annotations.Benchmark;
@@ -55,9 +56,9 @@ import org.openjdk.jmh.runner.options.OptionsBuilder;
 /// nested event payload, because that is what decides whether early exit can 
pay off. `documentBytes` pads the
 /// payload immediately before the late field so parser scaling is visible 
without changing the addressed values.
 ///
-/// The single-column benchmarks are also the per-row cost of 
`jsonExtractScalar`: that transform function
-/// does exactly `parseContext.parse(row).read(jsonPath)` per row, so 
measuring the extraction in isolation
-/// measures it without the surrounding `ValueBlock` scaffolding.
+/// The scalar-function methods measure the production `jsonPath*` ingestion 
wrappers. The lower-level single-column
+/// methods isolate parser/traversal strategies; 
[BenchmarkJsonExtractScalarQuery] is the authoritative query-transform
+/// measurement.
 @BenchmarkMode(Mode.Throughput)
 @OutputTimeUnit(TimeUnit.SECONDS)
 @Fork(1)
@@ -67,6 +68,9 @@ import org.openjdk.jmh.runner.options.OptionsBuilder;
 public class BenchmarkJsonPathExtraction {
   private static final Predicate[] NO_PREDICATES = new Predicate[0];
   private static final ForyJson FORY_JSON = ForyJson.builder().build();
+  private static final String STRING_DEFAULT = "DEFAULT";
+  private static final long LONG_DEFAULT = -1L;
+  private static final double DOUBLE_DEFAULT = -1.25d;
 
   /// Exactly the context `JsonExtractScalarTransformFunction` and 
`JsonFunctions` use.
   private static final ParseContext PARSE_CONTEXT = JsonPath.using(
@@ -75,7 +79,7 @@ public class BenchmarkJsonPathExtraction {
 
   private static final String BASE_JSON = "{"
       + "\"ts\":1719878400123,"
-      + 
"\"user\":{\"id\":\"u-19283\",\"country\":\"US\",\"tier\":\"gold\",\"age\":41},"
+      + 
"\"user\":{\"id\":\"u-19283\",\"country\":\"US\",\"score\":17.25,\"age\":41},"
       + 
"\"event\":{\"name\":\"checkout\",\"cart\":[{\"sku\":\"A1\",\"qty\":2,\"price\":19.99},"
       + 
"{\"sku\":\"B7\",\"qty\":1,\"price\":149.5},{\"sku\":\"C3\",\"qty\":5,\"price\":3.25}],"
       + "\"total\":352.73,\"currency\":\"USD\"},"
@@ -85,7 +89,7 @@ public class BenchmarkJsonPathExtraction {
       + 
"\"tags\":[\"mobile\",\"ios\",\"returning\",\"promo-eligible\",\"newsletter\"],"
       + 
"\"session\":{\"id\":\"s-aaaabbbbccccdddd\",\"start\":1719878300000,\"pages\":14,\"referrer\":"
       + "\"https://example.com/landing?utm_source=x&utm_medium=y\"},";
-      + "\"trailer\":{\"country\":\"DE\",\"note\":\"last field in the 
document\"}"
+      + "\"trailer\":{\"country\":\"DE\",\"double\":19.25,\"long\":19}"
       + "}";
 
   /// Four derived columns, spread through the document, as an ingestion 
`transformConfigs` would pull.
@@ -98,9 +102,17 @@ public class BenchmarkJsonPathExtraction {
   @Param({"700", "8192", "65536"})
   private int _documentBytes;
 
+  /// Use `-p _valueCase=missing` to measure the explicit-default path without 
doubling the default benchmark suite.
+  @Param({"hit"})
+  private String _valueCase;
+
   private String _json;
   private String _path;
+  private String _longPath;
+  private String _doublePath;
   private SimpleJsonPath _simplePath;
+  private SimpleJsonPath _simpleLongPath;
+  private SimpleJsonPath _simpleDoublePath;
   private SimpleJsonPath[] _simpleFourPaths;
   private Object[] _fourResults;
 
@@ -113,19 +125,44 @@ public class BenchmarkJsonPathExtraction {
 
   @Setup(Level.Trial)
   public void setUp() {
+    if (!ForyJsonPathExtractor.isAvailable()) {
+      throw new IllegalStateException("Fory JSON is unavailable; refusing to 
publish fallback results as Fory");
+    }
     _json = buildJson(_documentBytes);
-    _path = "early".equals(_fieldPosition) ? "$.user.country" : 
"$.trailer.country";
+    boolean missing;
+    if ("hit".equals(_valueCase)) {
+      missing = false;
+    } else if ("missing".equals(_valueCase)) {
+      missing = true;
+    } else {
+      throw new IllegalArgumentException("Unsupported value case: " + 
_valueCase);
+    }
+    if (missing) {
+      _path = "$.missing";
+      _longPath = _path;
+      _doublePath = _path;
+    } else if ("early".equals(_fieldPosition)) {
+      _path = "$.user.country";
+      _longPath = "$.user.age";
+      _doublePath = "$.user.score";
+    } else {
+      _path = "$.trailer.country";
+      _longPath = "$.trailer.long";
+      _doublePath = "$.trailer.double";
+    }
     _simplePath = SimpleJsonPath.compile(_path);
+    _simpleLongPath = SimpleJsonPath.compile(_longPath);
+    _simpleDoublePath = SimpleJsonPath.compile(_doublePath);
     _simpleFourPaths = new SimpleJsonPath[FOUR_PATHS.length];
     for (int i = 0; i < FOUR_PATHS.length; i++) {
       _simpleFourPaths[i] = SimpleJsonPath.compile(FOUR_PATHS[i]);
     }
     _fourResults = new Object[FOUR_PATHS.length];
 
-    String expected = "early".equals(_fieldPosition) ? "US" : "DE";
-    if (!expected.equals(JsonFunctions.jsonPathStringFory(_json, _path, ""))) {
-      throw new IllegalStateException("Fory result does not match the expected 
value for " + _path);
-    }
+    String expectedString = missing ? STRING_DEFAULT : 
"early".equals(_fieldPosition) ? "US" : "DE";
+    long expectedLong = missing ? LONG_DEFAULT : 
"early".equals(_fieldPosition) ? 41L : 19L;
+    double expectedDouble = missing ? DOUBLE_DEFAULT : 
"early".equals(_fieldPosition) ? 17.25d : 19.25d;
+    verifyScalarFunctions(expectedString, expectedLong, expectedDouble);
     for (String path : FOUR_PATHS) {
       String jayway = JsonFunctions.jsonPathString(_json, path, "");
       String fory = JsonFunctions.jsonPathStringFory(_json, path, "");
@@ -139,6 +176,42 @@ public class BenchmarkJsonPathExtraction {
     }
   }
 
+  private void verifyScalarFunctions(String expectedString, long expectedLong, 
double expectedDouble) {
+    Object extractedString = ForyJsonPathExtractor.extract(_json, _simplePath);
+    Object extractedLong = ForyJsonPathExtractor.extract(_json, 
_simpleLongPath);
+    Object extractedDouble = ForyJsonPathExtractor.extract(_json, 
_simpleDoublePath);
+    if ("missing".equals(_valueCase)) {
+      if (extractedString != null || extractedLong != null || extractedDouble 
!= null) {
+        throw new IllegalStateException("Fory returned a value for a missing 
benchmark path");
+      }
+    } else if (!expectedString.equals(extractedString) || ((Number) 
extractedLong).longValue() != expectedLong
+        || Double.compare(((Number) extractedDouble).doubleValue(), 
expectedDouble) != 0) {
+      throw new IllegalStateException("Direct Fory extraction produced an 
unexpected benchmark result");
+    }
+
+    String jaywayString = JsonFunctions.jsonPathString(_json, _path, 
STRING_DEFAULT);
+    String fastString = JsonFunctions.jsonPathStringFast(_json, _path, 
STRING_DEFAULT);
+    String foryString = JsonFunctions.jsonPathStringFory(_json, _path, 
STRING_DEFAULT);
+    if (!expectedString.equals(jaywayString) || 
!jaywayString.equals(fastString) || !jaywayString.equals(foryString)) {
+      throw new IllegalStateException("JSON string extractors disagree for " + 
_path);
+    }
+
+    long jaywayLong = JsonFunctions.jsonPathLong(_json, _longPath, 
LONG_DEFAULT);
+    long fastLong = JsonFunctions.jsonPathLongFast(_json, _longPath, 
LONG_DEFAULT);
+    long foryLong = JsonFunctions.jsonPathLongFory(_json, _longPath, 
LONG_DEFAULT);
+    if (jaywayLong != expectedLong || fastLong != jaywayLong || foryLong != 
jaywayLong) {
+      throw new IllegalStateException("JSON long extractors disagree for " + 
_longPath);
+    }
+
+    double jaywayDouble = JsonFunctions.jsonPathDouble(_json, _doublePath, 
DOUBLE_DEFAULT);
+    double fastDouble = JsonFunctions.jsonPathDoubleFast(_json, _doublePath, 
DOUBLE_DEFAULT);
+    double foryDouble = JsonFunctions.jsonPathDoubleFory(_json, _doublePath, 
DOUBLE_DEFAULT);
+    if (Double.compare(jaywayDouble, expectedDouble) != 0 || 
Double.compare(fastDouble, jaywayDouble) != 0
+        || Double.compare(foryDouble, jaywayDouble) != 0) {
+      throw new IllegalStateException("JSON double extractors disagree for " + 
_doublePath);
+    }
+  }
+
   private static String buildJson(int targetBytes) {
     if (BASE_JSON.length() >= targetBytes) {
       return BASE_JSON;
@@ -160,7 +233,7 @@ public class BenchmarkJsonPathExtraction {
     return PARSE_CONTEXT.parse(_json).read(_path, NO_PREDICATES);
   }
 
-  /// Fory parse plus the same Jayway tree traversal used by the production 
wrapper.
+  /// Fory dynamic-tree parsing plus Jayway traversal, retained as a 
non-production comparison baseline.
   @Benchmark
   public Object foryOneColumn() {
     Object root = FORY_JSON.fromJson(_json, Object.class);
@@ -180,25 +253,61 @@ public class BenchmarkJsonPathExtraction {
   /// The existing Jayway scalar function (applicability check + String 
coercion).
   @Benchmark
   public String jsonPathStringJayway() {
-    return JsonFunctions.jsonPathString(_json, _path, "");
+    return JsonFunctions.jsonPathString(_json, _path, STRING_DEFAULT);
   }
 
   /// The production Fory scalar function (applicability check + String 
coercion).
   @Benchmark
   public String jsonPathStringFory() {
-    return JsonFunctions.jsonPathStringFory(_json, _path, "");
+    return JsonFunctions.jsonPathStringFory(_json, _path, STRING_DEFAULT);
   }
 
-  /// The opt-in fast scalar function, full scan (exact parity).
+  /// The opt-in fast scalar function, full scan with fallback for unsupported 
or failed extraction.
   @Benchmark
   public String jsonPathStringFast() {
-    return JsonFunctions.jsonPathStringFast(_json, _path, "");
+    return JsonFunctions.jsonPathStringFast(_json, _path, STRING_DEFAULT);
   }
 
   /// The opt-in fast scalar function, early exit / first match.
   @Benchmark
   public String jsonPathStringFirstMatch() {
-    return JsonFunctions.jsonPathStringFirstMatch(_json, _path, "");
+    return JsonFunctions.jsonPathStringFirstMatch(_json, _path, 
STRING_DEFAULT);
+  }
+
+  /// The existing Jayway scalar function (applicability check + long 
coercion).
+  @Benchmark
+  public long jsonPathLongJayway() {
+    return JsonFunctions.jsonPathLong(_json, _longPath, LONG_DEFAULT);
+  }
+
+  /// The opt-in fast scalar function, full scan with fallback for unsupported 
or failed extraction.
+  @Benchmark
+  public long jsonPathLongFast() {
+    return JsonFunctions.jsonPathLongFast(_json, _longPath, LONG_DEFAULT);
+  }
+
+  /// The production Fory scalar function (applicability check + long 
coercion).
+  @Benchmark
+  public long jsonPathLongFory() {
+    return JsonFunctions.jsonPathLongFory(_json, _longPath, LONG_DEFAULT);
+  }
+
+  /// The existing Jayway scalar function (applicability check + double 
coercion).
+  @Benchmark
+  public double jsonPathDoubleJayway() {
+    return JsonFunctions.jsonPathDouble(_json, _doublePath, DOUBLE_DEFAULT);
+  }
+
+  /// The opt-in fast scalar function, full scan with fallback for unsupported 
or failed extraction.
+  @Benchmark
+  public double jsonPathDoubleFast() {
+    return JsonFunctions.jsonPathDoubleFast(_json, _doublePath, 
DOUBLE_DEFAULT);
+  }
+
+  /// The production Fory scalar function (applicability check + double 
coercion).
+  @Benchmark
+  public double jsonPathDoubleFory() {
+    return JsonFunctions.jsonPathDoubleFory(_json, _doublePath, 
DOUBLE_DEFAULT);
   }
 
   @Benchmark


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to