Skip to content

Commit a969838

Browse files
committed
add reproduction test case for dynamic template bug
this adds a test case to reproduce #1513 essentially this is a port of opensearch-project/spring-data-opensearch#443 which purely uses the `opensearch-java` APIs rather than going through the Spring APIs. this does not attempt to fix the bug, it just adds an ignored test case which will make it easier to reproduce & fix the bug. Signed-off-by: Ralph Ursprung <Ralph.Ursprung@avaloq.com>
1 parent c16f50f commit a969838

3 files changed

Lines changed: 195 additions & 0 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.client.opensearch.integTest;
10+
11+
import jakarta.json.stream.JsonParser;
12+
import java.io.IOException;
13+
import java.io.StringReader;
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
import org.junit.Ignore;
17+
import org.junit.Test;
18+
import org.opensearch.client.json.JsonpMapper;
19+
import org.opensearch.client.json.jackson.JacksonJsonpMapper;
20+
import org.opensearch.client.opensearch._types.Refresh;
21+
import org.opensearch.client.opensearch._types.mapping.TypeMapping;
22+
import org.opensearch.client.opensearch.core.IndexResponse;
23+
import org.opensearch.client.opensearch.core.SearchResponse;
24+
25+
public abstract class AbstractDynamicTemplateIT extends OpenSearchJavaClientTestCase {
26+
private static final String INDEX_NAME_ONE = "dynamic-templates-test-one";
27+
private static final String INDEX_NAME_TWO = "dynamic-templates-test-two";
28+
29+
@Test
30+
public void shouldCreateDynamicTemplateOne() throws IOException {
31+
// Create index with dynamic templates
32+
final String dynamicTemplatesMappingOne = """
33+
{
34+
"dynamic_templates": [
35+
{
36+
"with_custom_analyzer": {
37+
"mapping": {
38+
"type": "text",
39+
"analyzer": "standard"
40+
},
41+
"path_match": "names.*"
42+
}
43+
}
44+
]
45+
}
46+
""";
47+
48+
final TypeMapping typeMapping = parseMappingJson(dynamicTemplatesMappingOne);
49+
50+
javaClient().indices().create(builder -> builder.index(INDEX_NAME_ONE).mappings(typeMapping));
51+
52+
// Index a document with a Map containing a key with a DOT
53+
// This is the problematic case: "first.last" as a property name
54+
final Map<String, Object> document = new HashMap<>();
55+
document.put("id", "1");
56+
57+
final Map<String, String> names = new HashMap<>();
58+
names.put("John", "Smith");
59+
document.put("names", names);
60+
61+
final IndexResponse indexResponse = javaClient().index(
62+
builder -> builder.index(INDEX_NAME_ONE).id("1").document(document).refresh(Refresh.True)
63+
);
64+
65+
assertEquals("Created", indexResponse.result().name());
66+
67+
// Search to verify the document was indexed
68+
final SearchResponse<Map> searchResponse = javaClient().search(
69+
builder -> builder.index(INDEX_NAME_ONE).query(q -> q.matchAll(m -> m)),
70+
Map.class
71+
);
72+
73+
assertEquals(1L, searchResponse.hits().total().value());
74+
75+
// Verify we can retrieve the nested property value
76+
@SuppressWarnings("unchecked")
77+
final Map<String, Object> source = searchResponse.hits().hits().get(0).source();
78+
assertNotNull(source);
79+
assertTrue(source.get("names") instanceof Map);
80+
81+
@SuppressWarnings("unchecked")
82+
final Map<String, String> retrievedNames = (Map<String, String>) source.get("names");
83+
assertEquals("Smith", retrievedNames.get("John"));
84+
}
85+
86+
@Test
87+
@Ignore("known bug, see https://github.com/opensearch-project/opensearch-java/issues/1513")
88+
public void shouldCreateDynamicTemplateTwo() throws IOException {
89+
// Create index with dynamic templates
90+
final String dynamicTemplatesMappingTwo = """
91+
{
92+
"dynamic_templates": [
93+
{
94+
"keywords_without_doc_values": {
95+
"match_mapping_type": "string",
96+
"mapping": {
97+
"fields": {
98+
"keyword": {
99+
"type": "keyword",
100+
"doc_values": false
101+
}
102+
}
103+
},
104+
"path_match": "names.*"
105+
}
106+
}
107+
]
108+
}
109+
""";
110+
111+
final TypeMapping typeMapping = parseMappingJson(dynamicTemplatesMappingTwo);
112+
113+
javaClient().indices().create(builder -> builder.index(INDEX_NAME_TWO).mappings(typeMapping));
114+
115+
// Index a document with a Map containing a key with a DOT
116+
// This is the problematic case: "first.last" as a property name
117+
final Map<String, Object> document = new HashMap<>();
118+
document.put("id", "1");
119+
120+
final Map<String, String> names = new HashMap<>();
121+
names.put("first.last", "Smith"); // Property name contains a dot
122+
document.put("names", names);
123+
124+
final IndexResponse indexResponse = javaClient().index(
125+
builder -> builder.index(INDEX_NAME_TWO).id("1").document(document).refresh(Refresh.True)
126+
);
127+
128+
assertEquals("Created", indexResponse.result().name());
129+
130+
// Search to verify the document was indexed
131+
final SearchResponse<Map> searchResponse = javaClient().search(
132+
builder -> builder.index(INDEX_NAME_TWO).query(q -> q.matchAll(m -> m)),
133+
Map.class
134+
);
135+
136+
assertEquals(1L, searchResponse.hits().total().value());
137+
138+
// Verify we can retrieve the nested property value
139+
@SuppressWarnings("unchecked")
140+
final Map<String, Object> source = searchResponse.hits().hits().get(0).source();
141+
assertNotNull(source);
142+
assertTrue(source.get("names") instanceof Map);
143+
144+
@SuppressWarnings("unchecked")
145+
final Map<String, String> retrievedNames = (Map<String, String>) source.get("names");
146+
assertEquals("Smith", retrievedNames.get("first.last"));
147+
}
148+
149+
/**
150+
* Parse JSON mapping string into TypeMapping object.
151+
*/
152+
private TypeMapping parseMappingJson(final String json) {
153+
final JsonpMapper mapper = new JacksonJsonpMapper();
154+
try (final JsonParser parser = mapper.jsonProvider().createParser(new StringReader(json))) {
155+
return mapper.deserialize(parser, TypeMapping.class);
156+
}
157+
}
158+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.client.opensearch.integTest.httpclient5;
10+
11+
import org.opensearch.client.opensearch.integTest.AbstractDynamicTemplateIT;
12+
13+
public class DynamicTemplateIT extends AbstractDynamicTemplateIT implements HttpClient5TransportSupport {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.client.opensearch.integTest.restclient;
10+
11+
import java.io.IOException;
12+
import org.apache.hc.core5.http.HttpHost;
13+
import org.opensearch.client.json.jackson.JacksonJsonpMapper;
14+
import org.opensearch.client.opensearch.integTest.AbstractDynamicTemplateIT;
15+
import org.opensearch.client.transport.OpenSearchTransport;
16+
import org.opensearch.client.transport.rest_client.RestClientTransport;
17+
import org.opensearch.common.settings.Settings;
18+
19+
public class DynamicTemplateIT extends AbstractDynamicTemplateIT {
20+
@Override
21+
public OpenSearchTransport buildTransport(Settings settings, HttpHost[] hosts) throws IOException {
22+
return new RestClientTransport(buildClient(settings, hosts), new JacksonJsonpMapper());
23+
}
24+
}

0 commit comments

Comments
 (0)