Browse Source

Add tests where patterned_text is nested in object, nested, and passthrough objects (#134831)

Add some tests where patterned_test is nested within objects, nested objects, and passthrough objects. Found a bug with phrase queries when patterned_text is in nested objects: #134830
Parker Timmins 1 month ago
parent
commit
c9b2f2568d

+ 263 - 0
x-pack/plugin/logsdb/src/test/java/org/elasticsearch/xpack/logsdb/patternedtext/PatternedTextNestedObjectTests.java

@@ -0,0 +1,263 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.logsdb.patternedtext;
+
+import org.apache.lucene.search.join.ScoreMode;
+import org.elasticsearch.action.DocWriteRequest;
+import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
+import org.elasticsearch.action.bulk.BulkRequest;
+import org.elasticsearch.action.bulk.BulkResponse;
+import org.elasticsearch.action.index.IndexRequest;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.action.support.IndicesOptions;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.index.IndexSettings;
+import org.elasticsearch.index.mapper.extras.MapperExtrasPlugin;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.license.LicenseSettings;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.test.ESSingleNodeTestCase;
+import org.elasticsearch.xcontent.ObjectPath;
+import org.elasticsearch.xcontent.XContentType;
+import org.elasticsearch.xpack.core.XPackPlugin;
+import org.elasticsearch.xpack.logsdb.LogsDBPlugin;
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse;
+
+public class PatternedTextNestedObjectTests extends ESSingleNodeTestCase {
+
+    @Override
+    protected Settings nodeSettings() {
+        return Settings.builder().put(LicenseSettings.SELF_GENERATED_LICENSE_TYPE.getKey(), "trial").build();
+    }
+
+    @Override
+    protected Collection<Class<? extends Plugin>> getPlugins() {
+        return List.of(MapperExtrasPlugin.class, XPackPlugin.class, LogsDBPlugin.class);
+    }
+
+    private static final String INDEX = "test_index";
+
+    private static final String SHORT_MESSAGE = "some message 123 ";
+    private static final String LONG_MESSAGE = SHORT_MESSAGE.repeat(((32 * 1024) / SHORT_MESSAGE.length()) + 1);
+
+    private static final Settings SYNTHETIC_SETTING = Settings.builder()
+        .put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic")
+        .build();
+
+    @Before
+    public void setup() {
+        assumeTrue("Only when patterned_text feature flag is enabled", PatternedTextFieldMapper.PATTERNED_TEXT_MAPPER.isEnabled());
+    }
+
+    @After
+    public void cleanup() {
+        assertAcked(admin().indices().prepareDelete(INDEX).setIndicesOptions(IndicesOptions.lenientExpandOpen()).get());
+    }
+
+    public void testInObject() {
+        String mapping = """
+                {
+                  "properties": {
+                    "obj": {
+                      "type": "object",
+                      "properties": {
+                        "field_patterned_text": {
+                          "type": "patterned_text"
+                        }
+                      }
+                    }
+                  }
+                }
+            """;
+
+        var createRequest = indicesAdmin().prepareCreate(INDEX).setSettings(SYNTHETIC_SETTING).setMapping(mapping);
+        createIndex(INDEX, createRequest);
+
+        String message = randomBoolean() ? SHORT_MESSAGE : LONG_MESSAGE;
+        indexDoc("""
+            {
+                "obj.field_patterned_text": "%"
+            }
+            """.replace("%", message));
+
+        var actualMappings = getMapping();
+        assertEquals("patterned_text", ObjectPath.eval("properties.obj.properties.field_patterned_text.type", actualMappings));
+
+        var query = randomBoolean()
+            ? QueryBuilders.matchQuery("obj.field_patterned_text", SHORT_MESSAGE)
+            : QueryBuilders.matchPhraseQuery("obj.field_patterned_text", SHORT_MESSAGE);
+        var searchRequest = client().prepareSearch(INDEX).setQuery(query).setSize(1);
+
+        assertNoFailuresAndResponse(searchRequest, searchResponse -> {
+            assertEquals(Set.of(message), getFieldValuesFromSource(searchResponse, "obj.field_patterned_text"));
+        });
+    }
+
+    public void testInObjectInObject() {
+        String mapping = """
+                {
+                  "properties": {
+                    "obj": {
+                      "type": "object",
+                      "properties": {
+                        "inner": {
+                            "type": "object",
+                            "properties": {
+                                "field_patterned_text": {
+                                  "type": "patterned_text"
+                                }
+                            }
+                        }
+                      }
+                    }
+                  }
+                }
+            """;
+
+        var createRequest = indicesAdmin().prepareCreate(INDEX).setSettings(SYNTHETIC_SETTING).setMapping(mapping);
+        createIndex(INDEX, createRequest);
+
+        String message = randomBoolean() ? SHORT_MESSAGE : LONG_MESSAGE;
+        indexDoc("""
+            {
+                "obj.inner.field_patterned_text": "%"
+            }
+            """.replace("%", message));
+
+        var actualMappings = getMapping();
+        assertEquals(
+            "patterned_text",
+            ObjectPath.eval("properties.obj.properties.inner.properties.field_patterned_text.type", actualMappings)
+        );
+
+        var query = randomBoolean()
+            ? QueryBuilders.matchQuery("obj.inner.field_patterned_text", SHORT_MESSAGE)
+            : QueryBuilders.matchPhraseQuery("obj.inner.field_patterned_text", SHORT_MESSAGE);
+        var searchRequest = client().prepareSearch(INDEX).setQuery(query).setSize(1);
+
+        assertNoFailuresAndResponse(searchRequest, searchResponse -> {
+            assertEquals(Set.of(message), getFieldValuesFromSource(searchResponse, "obj.inner.field_patterned_text"));
+        });
+    }
+
+    @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/134830")
+    public void testInNested() {
+        String mapping = """
+                {
+                  "properties": {
+                    "obj": {
+                      "type": "nested",
+                      "properties": {
+                        "field_patterned_text": {
+                          "type": "patterned_text"
+                        }
+                      }
+                    }
+                  }
+                }
+            """;
+
+        var createRequest = indicesAdmin().prepareCreate(INDEX).setSettings(SYNTHETIC_SETTING).setMapping(mapping);
+        createIndex(INDEX, createRequest);
+
+        String message = randomBoolean() ? SHORT_MESSAGE : LONG_MESSAGE;
+        indexDoc("""
+            {
+                "obj.field_patterned_text": "%"
+            }
+            """.replace("%", message));
+
+        var actualMappings = getMapping();
+        assertEquals("patterned_text", ObjectPath.eval("properties.obj.properties.field_patterned_text.type", actualMappings));
+
+        var innerQuery = randomBoolean()
+            ? QueryBuilders.matchQuery("obj.field_patterned_text", SHORT_MESSAGE)
+            : QueryBuilders.matchPhraseQuery("obj.field_patterned_text", SHORT_MESSAGE);
+        var query = QueryBuilders.nestedQuery("obj", innerQuery, ScoreMode.Avg);
+        var searchRequest = client().prepareSearch(INDEX).setQuery(query).setSize(1);
+
+        assertNoFailuresAndResponse(searchRequest, searchResponse -> {
+            assertEquals(Set.of(message), getFieldValuesFromSource(searchResponse, "obj.field_patterned_text"));
+        });
+    }
+
+    public void testInPassthrough() {
+        String mapping = """
+                {
+                  "properties": {
+                    "obj": {
+                      "type": "passthrough",
+                      "priority": 1,
+                      "properties": {
+                        "field_patterned_text": {
+                          "type": "patterned_text"
+                        }
+                      }
+                    }
+                  }
+                }
+            """;
+
+        var createRequest = indicesAdmin().prepareCreate(INDEX).setSettings(SYNTHETIC_SETTING).setMapping(mapping);
+        createIndex(INDEX, createRequest);
+
+        String message = randomBoolean() ? SHORT_MESSAGE : LONG_MESSAGE;
+        indexDoc("""
+            {
+                "obj.field_patterned_text": "%"
+            }
+            """.replace("%", message));
+
+        var actualMappings = getMapping();
+        assertEquals("patterned_text", ObjectPath.eval("properties.obj.properties.field_patterned_text.type", actualMappings));
+
+        var query = randomBoolean()
+            ? QueryBuilders.matchQuery("field_patterned_text", SHORT_MESSAGE)
+            : QueryBuilders.matchPhraseQuery("field_patterned_text", SHORT_MESSAGE);
+        var searchRequest = client().prepareSearch(INDEX).setQuery(query).setSize(1);
+
+        assertNoFailuresAndResponse(searchRequest, searchResponse -> {
+            assertEquals(Set.of(message), getFieldValuesFromSource(searchResponse, "obj.field_patterned_text"));
+        });
+    }
+
+    static Set<Object> getFieldValuesFromSource(SearchResponse response, String path) {
+        var values = new HashSet<>();
+        SearchHit[] hits = response.getHits().getHits();
+        for (SearchHit hit : hits) {
+            var sourceAsMap = hit.getSourceAsMap();
+            Object value = ObjectPath.eval(path, sourceAsMap);
+            values.add(value);
+        }
+        return values;
+    }
+
+    Map<String, Object> getMapping() {
+        return indicesAdmin().prepareGetMappings(TEST_REQUEST_TIMEOUT, INDEX).get().mappings().get(INDEX).sourceAsMap();
+    }
+
+    private void indexDoc(String doc) {
+        BulkRequest bulkRequest = new BulkRequest();
+        var indexRequest = new IndexRequest(INDEX).opType(DocWriteRequest.OpType.CREATE).source(doc, XContentType.JSON);
+        bulkRequest.add(indexRequest);
+        BulkResponse bulkResponse = client().bulk(bulkRequest).actionGet();
+        assertFalse(bulkResponse.hasFailures());
+        safeGet(indicesAdmin().refresh(new RefreshRequest(INDEX).indicesOptions(IndicesOptions.lenientExpandOpenHidden())));
+    }
+}