Forráskód Böngészése

[9.1] Bug fix: Facilitate second retrieval of the same value (#134770) (#134917)

Mary Gouseti 3 hete
szülő
commit
4cdca668a3

+ 6 - 0
docs/changelog/134790.yaml

@@ -0,0 +1,6 @@
+pr: 134790
+summary: "Bug fix: Facilitate second retrieval of the same value"
+area: Infra/Core
+type: bug
+issues:
+ - 134770

+ 17 - 10
libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/json/ESUTF8StreamJsonParser.java

@@ -27,6 +27,7 @@ import java.util.List;
 public class ESUTF8StreamJsonParser extends UTF8StreamJsonParser {
     protected int stringEnd = -1;
     protected int stringLength;
+    protected byte[] lastOptimisedValue;
 
     private final List<Integer> backslashes = new ArrayList<>();
 
@@ -51,6 +52,9 @@ public class ESUTF8StreamJsonParser extends UTF8StreamJsonParser {
      */
     public Text getValueAsText() throws IOException {
         if (_currToken == JsonToken.VALUE_STRING && _tokenIncomplete) {
+            if (lastOptimisedValue != null) {
+                return new Text(new XContentString.UTF8Bytes(lastOptimisedValue), stringLength);
+            }
             if (stringEnd > 0) {
                 final int len = stringEnd - 1 - _inputPtr;
                 return new Text(new XContentString.UTF8Bytes(_inputBuffer, _inputPtr, len), stringLength);
@@ -135,37 +139,40 @@ public class ESUTF8StreamJsonParser extends UTF8StreamJsonParser {
                 copyPtr = backslash + 1;
             }
             System.arraycopy(inputBuffer, copyPtr, buff, destPtr, ptr - copyPtr);
+            lastOptimisedValue = buff;
             return new Text(new XContentString.UTF8Bytes(buff), stringLength);
         }
     }
 
     @Override
     public JsonToken nextToken() throws IOException {
-        if (_currToken == JsonToken.VALUE_STRING && _tokenIncomplete && stringEnd > 0) {
-            _inputPtr = stringEnd;
-            _tokenIncomplete = false;
-        }
+        maybeResetCurrentTokenState();
         stringEnd = -1;
         return super.nextToken();
     }
 
     @Override
     public boolean nextFieldName(SerializableString str) throws IOException {
-        if (_currToken == JsonToken.VALUE_STRING && _tokenIncomplete && stringEnd > 0) {
-            _inputPtr = stringEnd;
-            _tokenIncomplete = false;
-        }
+        maybeResetCurrentTokenState();
         stringEnd = -1;
         return super.nextFieldName(str);
     }
 
     @Override
     public String nextFieldName() throws IOException {
+        maybeResetCurrentTokenState();
+        stringEnd = -1;
+        return super.nextFieldName();
+    }
+
+    /**
+     * Resets the current token state before moving to the next.
+     */
+    private void maybeResetCurrentTokenState() {
         if (_currToken == JsonToken.VALUE_STRING && _tokenIncomplete && stringEnd > 0) {
             _inputPtr = stringEnd;
             _tokenIncomplete = false;
+            lastOptimisedValue = null;
         }
-        stringEnd = -1;
-        return super.nextFieldName();
     }
 }

+ 28 - 4
libs/x-content/impl/src/test/java/org/elasticsearch/xcontent/provider/json/ESUTF8StreamJsonParserTests.java

@@ -57,14 +57,30 @@ public class ESUTF8StreamJsonParserTests extends ESTestCase {
             assertThat(parser.nextToken(), Matchers.equalTo(JsonToken.END_OBJECT));
         });
 
-        testParseJson("{\"foo\": \"bar\\\"baz\\\"\"}", parser -> {
+        testParseJson("{\"foo\": [\"bar\\\"baz\\\"\", \"foobar\"]}", parser -> {
             assertThat(parser.nextToken(), Matchers.equalTo(JsonToken.START_OBJECT));
             assertThat(parser.nextFieldName(), Matchers.equalTo("foo"));
+
+            assertThat(parser.nextValue(), Matchers.equalTo(JsonToken.START_ARRAY));
             assertThat(parser.nextValue(), Matchers.equalTo(JsonToken.VALUE_STRING));
 
-            var text = parser.getValueAsText();
-            assertThat(text, Matchers.notNullValue());
-            assertTextRef(text.bytes(), "bar\"baz\"");
+            var firstText = parser.getValueAsText();
+            assertThat(firstText, Matchers.notNullValue());
+            assertTextRef(firstText.bytes(), "bar\"baz\"");
+            // Retrieve the value for a second time to ensure the last value is available
+            firstText = parser.getValueAsText();
+            assertThat(firstText, Matchers.notNullValue());
+            assertTextRef(firstText.bytes(), "bar\"baz\"");
+
+            // Ensure values lastOptimisedValue is reset
+            assertThat(parser.nextValue(), Matchers.equalTo(JsonToken.VALUE_STRING));
+            var secondTest = parser.getValueAsText();
+            assertThat(secondTest, Matchers.notNullValue());
+            assertTextRef(secondTest.bytes(), "foobar");
+            secondTest = parser.getValueAsText();
+            assertThat(secondTest, Matchers.notNullValue());
+            assertTextRef(secondTest.bytes(), "foobar");
+            assertThat(parser.nextValue(), Matchers.equalTo(JsonToken.END_ARRAY));
         });
 
         testParseJson("{\"foo\": \"b\\u00e5r\"}", parser -> {
@@ -256,9 +272,17 @@ public class ESUTF8StreamJsonParserTests extends ESTestCase {
                     var text = parser.getValueAsText();
                     assertTextRef(text.bytes(), currVal);
                     assertThat(text.stringLength(), Matchers.equalTo(currVal.length()));
+
+                    // Retrieve it twice to ensure it works as expected
+                    text = parser.getValueAsText();
+                    assertTextRef(text.bytes(), currVal);
+                    assertThat(text.stringLength(), Matchers.equalTo(currVal.length()));
                 } else {
                     assertThat(parser.getValueAsText(), Matchers.nullValue());
                     assertThat(parser.getValueAsString(), Matchers.equalTo(currVal));
+                    // Retrieve it twice to ensure it works as expected
+                    assertThat(parser.getValueAsText(), Matchers.nullValue());
+                    assertThat(parser.getValueAsString(), Matchers.equalTo(currVal));
                 }
             }
         });

+ 49 - 0
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/mapping/30_multi_field_keyword.yml

@@ -0,0 +1,49 @@
+---
+Keyword with escaped characters as multi-field:
+  - requires:
+      cluster_features: [ "mapper.multi_field.unicode_optimisation_fix" ]
+      reason: "requires a fix (#134770)"
+  - do:
+      indices.create:
+        index: test
+        body:
+          mappings:
+            properties:
+              foo:
+                type: keyword
+                fields:
+                  bar:
+                    type: keyword
+
+  - do:
+      index:
+        index: test
+        id:    "1"
+        refresh: true
+        body:
+          foo: "c:\\windows\\system32\\svchost.exe"
+
+  - do:
+      search:
+        index: test
+        body:
+          query:
+            term:
+              foo: "c:\\windows\\system32\\svchost.exe"
+
+  - match: { "hits.total.value": 1 }
+  - match:
+      hits.hits.0._source.foo: "c:\\windows\\system32\\svchost.exe"
+
+  # Test that optimisation works the same for the multi-fields as well.
+  - do:
+      search:
+        index: test
+        body:
+          query:
+            term:
+              foo.bar: "c:\\windows\\system32\\svchost.exe"
+
+  - match: { "hits.total.value": 1 }
+  - match:
+      hits.hits.0._source.foo: "c:\\windows\\system32\\svchost.exe"

+ 3 - 1
server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java

@@ -46,6 +46,7 @@ public class MapperFeatures implements FeatureSpecification {
     static final NodeFeature IVF_NESTED_SUPPORT = new NodeFeature("mapper.ivf_nested_support");
     static final NodeFeature SEARCH_LOAD_PER_SHARD = new NodeFeature("mapper.search_load_per_shard");
     static final NodeFeature PATTERNED_TEXT = new NodeFeature("mapper.patterned_text");
+    public static final NodeFeature MULTI_FIELD_UNICODE_OPTIMISATION_FIX = new NodeFeature("mapper.multi_field.unicode_optimisation_fix");
 
     @Override
     public Set<NodeFeature> getTestFeatures() {
@@ -78,7 +79,8 @@ public class MapperFeatures implements FeatureSpecification {
             IVF_NESTED_SUPPORT,
             SEARCH_LOAD_PER_SHARD,
             SPARSE_VECTOR_INDEX_OPTIONS_FEATURE,
-            PATTERNED_TEXT
+            PATTERNED_TEXT,
+            MULTI_FIELD_UNICODE_OPTIMISATION_FIX
         );
     }
 }