Skip to content

Commit 6036e69

Browse files
committed
Fix source filter setup in multiget requests.
Closes spring-projects#1659
1 parent 1aabb42 commit 6036e69

File tree

3 files changed

+293
-27
lines changed

3 files changed

+293
-27
lines changed

src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java

+53-27
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import org.elasticsearch.script.Script;
8181
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
8282
import org.elasticsearch.search.builder.SearchSourceBuilder;
83+
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
8384
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
8485
import org.elasticsearch.search.sort.FieldSortBuilder;
8586
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
@@ -861,9 +862,7 @@ private List<MultiGetRequest.Item> getMultiRequestItems(Query searchQuery, Class
861862
elasticsearchConverter.updateQuery(searchQuery, clazz);
862863
List<MultiGetRequest.Item> items = new ArrayList<>();
863864

864-
if (!isEmpty(searchQuery.getFields())) {
865-
searchQuery.addSourceFilter(new FetchSourceFilter(toArray(searchQuery.getFields()), null));
866-
}
865+
FetchSourceContext fetchSourceContext = getFetchSourceContext(searchQuery);
867866

868867
if (!isEmpty(searchQuery.getIds())) {
869868
String indexName = index.getIndexName();
@@ -873,6 +872,11 @@ private List<MultiGetRequest.Item> getMultiRequestItems(Query searchQuery, Class
873872
if (searchQuery.getRoute() != null) {
874873
item = item.routing(searchQuery.getRoute());
875874
}
875+
876+
if (fetchSourceContext != null) {
877+
item.fetchSourceContext(fetchSourceContext);
878+
}
879+
876880
items.add(item);
877881
}
878882
}
@@ -1732,38 +1736,29 @@ public static WriteRequest.RefreshPolicy toElasticsearchRefreshPolicy(RefreshPol
17321736
return WriteRequest.RefreshPolicy.NONE;
17331737
}
17341738
}
1735-
// region response stuff
1736-
1737-
/**
1738-
* extract the index settings information for a given index
1739-
*
1740-
* @param response the Elasticsearch response
1741-
* @param indexName the index name
1742-
* @return settings as {@link Document}
1743-
*/
1744-
public Document fromSettingsResponse(GetSettingsResponse response, String indexName) {
17451739

1746-
Document settings = Document.create();
1740+
private FetchSourceContext getFetchSourceContext(Query searchQuery) {
1741+
FetchSourceContext fetchSourceContext = null;
1742+
SourceFilter sourceFilter = searchQuery.getSourceFilter();
17471743

1748-
if (!response.getIndexToDefaultSettings().isEmpty()) {
1749-
Settings defaultSettings = response.getIndexToDefaultSettings().get(indexName);
1750-
for (String key : defaultSettings.keySet()) {
1751-
settings.put(key, defaultSettings.get(key));
1744+
if (!isEmpty(searchQuery.getFields())) {
1745+
if (sourceFilter == null) {
1746+
sourceFilter = new FetchSourceFilter(toArray(searchQuery.getFields()), null);
1747+
} else {
1748+
ArrayList<String> arrayList = new ArrayList<>();
1749+
Collections.addAll(arrayList, sourceFilter.getIncludes());
1750+
sourceFilter = new FetchSourceFilter(toArray(arrayList), null);
17521751
}
1753-
}
17541752

1755-
if (!response.getIndexToSettings().isEmpty()) {
1756-
Settings customSettings = response.getIndexToSettings().get(indexName);
1757-
for (String key : customSettings.keySet()) {
1758-
settings.put(key, customSettings.get(key));
1759-
}
1753+
fetchSourceContext = new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes());
1754+
} else if (sourceFilter != null) {
1755+
fetchSourceContext = new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes());
17601756
}
1761-
1762-
return settings;
1757+
return fetchSourceContext;
17631758
}
1759+
17641760
// endregion
17651761

1766-
// region helper functions
17671762
@Nullable
17681763
private ElasticsearchPersistentEntity<?> getPersistentEntity(@Nullable Class<?> clazz) {
17691764
return clazz != null ? elasticsearchConverter.getMappingContext().getPersistentEntity(clazz) : null;
@@ -1838,4 +1833,35 @@ private Script getScript(UpdateQuery query) {
18381833

18391834
// endregion
18401835

1836+
// region response stuff
1837+
1838+
/**
1839+
* extract the index settings information for a given index
1840+
*
1841+
* @param response the Elasticsearch response
1842+
* @param indexName the index name
1843+
* @return settings as {@link Document}
1844+
*/
1845+
public Document fromSettingsResponse(GetSettingsResponse response, String indexName) {
1846+
1847+
Document settings = Document.create();
1848+
1849+
if (!response.getIndexToDefaultSettings().isEmpty()) {
1850+
Settings defaultSettings = response.getIndexToDefaultSettings().get(indexName);
1851+
for (String key : defaultSettings.keySet()) {
1852+
settings.put(key, defaultSettings.get(key));
1853+
}
1854+
}
1855+
1856+
if (!response.getIndexToSettings().isEmpty()) {
1857+
Settings customSettings = response.getIndexToSettings().get(indexName);
1858+
for (String key : customSettings.keySet()) {
1859+
settings.put(key, customSettings.get(key));
1860+
}
1861+
}
1862+
1863+
return settings;
1864+
}
1865+
1866+
// endregion
18411867
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import lombok.AllArgsConstructor;
21+
import lombok.Builder;
22+
import lombok.Data;
23+
import lombok.NoArgsConstructor;
24+
25+
import java.util.Collections;
26+
import java.util.List;
27+
28+
import org.junit.jupiter.api.AfterEach;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.DisplayName;
31+
import org.junit.jupiter.api.Test;
32+
import org.springframework.beans.factory.annotation.Autowired;
33+
import org.springframework.data.annotation.Id;
34+
import org.springframework.data.elasticsearch.annotations.Document;
35+
import org.springframework.data.elasticsearch.annotations.Field;
36+
import org.springframework.data.elasticsearch.annotations.FieldType;
37+
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
38+
import org.springframework.data.elasticsearch.core.query.Query;
39+
import org.springframework.data.elasticsearch.core.query.SourceFilter;
40+
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
41+
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
42+
import org.springframework.test.context.ContextConfiguration;
43+
44+
/**
45+
* @author Peter-Josef Meisch
46+
*/
47+
@SpringIntegrationTest
48+
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
49+
public class SourceFilterIntegrationTests {
50+
51+
@Autowired private ElasticsearchOperations operations;
52+
private IndexOperations indexOps;
53+
54+
@BeforeEach
55+
void setUp() {
56+
indexOps = operations.indexOps(Entity.class);
57+
indexOps.create();
58+
indexOps.putMapping();
59+
60+
operations.save(Entity.builder().id("42").field1("one").field2("two").field3("three").build());
61+
}
62+
63+
@AfterEach
64+
void tearDown() {
65+
indexOps.delete();
66+
}
67+
68+
@Test // #1659
69+
@DisplayName("should only return requested fields on search")
70+
void shouldOnlyReturnRequestedFieldsOnSearch() {
71+
72+
Query query = Query.findAll();
73+
query.addFields("field2");
74+
75+
SearchHits<Entity> searchHits = operations.search(query, Entity.class);
76+
77+
assertThat(searchHits).hasSize(1);
78+
Entity entity = searchHits.getSearchHit(0).getContent();
79+
assertThat(entity.getField1()).isNull();
80+
assertThat(entity.getField2()).isEqualTo("two");
81+
assertThat(entity.getField3()).isNull();
82+
}
83+
84+
@Test // #1659
85+
@DisplayName("should only return requested fields on multiget")
86+
void shouldOnlyReturnRequestedFieldsOnGMultiGet() {
87+
88+
Query query = new NativeSearchQueryBuilder().withIds(Collections.singleton("42")).build();
89+
query.addFields("field2");
90+
91+
List<Entity> entities = operations.multiGet(query, Entity.class);
92+
93+
assertThat(entities).hasSize(1);
94+
Entity entity = entities.get(0);
95+
assertThat(entity.getField1()).isNull();
96+
assertThat(entity.getField2()).isEqualTo("two");
97+
assertThat(entity.getField3()).isNull();
98+
}
99+
100+
@Test // #1659
101+
@DisplayName("should not return excluded fields from SourceFilter on search")
102+
void shouldNotReturnExcludedFieldsFromSourceFilterOnSearch() {
103+
104+
Query query = Query.findAll();
105+
query.addSourceFilter(new SourceFilter() {
106+
@Override
107+
public String[] getIncludes() {
108+
return new String[] {};
109+
}
110+
111+
@Override
112+
public String[] getExcludes() {
113+
return new String[] { "field2" };
114+
}
115+
});
116+
117+
SearchHits<Entity> entities = operations.search(query, Entity.class);
118+
119+
assertThat(entities).hasSize(1);
120+
Entity entity = entities.getSearchHit(0).getContent();
121+
assertThat(entity.getField1()).isNotNull();
122+
assertThat(entity.getField2()).isNull();
123+
assertThat(entity.getField3()).isNotNull();
124+
}
125+
126+
@Test // #1659
127+
@DisplayName("should not return excluded fields from SourceFilter on multiget")
128+
void shouldNotReturnExcludedFieldsFromSourceFilterOnMultiGet() {
129+
130+
Query query = new NativeSearchQueryBuilder().withIds(Collections.singleton("42")).build();
131+
query.addSourceFilter(new SourceFilter() {
132+
@Override
133+
public String[] getIncludes() {
134+
return new String[] {};
135+
}
136+
137+
@Override
138+
public String[] getExcludes() {
139+
return new String[] { "field2" };
140+
}
141+
});
142+
143+
List<Entity> entities = operations.multiGet(query, Entity.class);
144+
145+
assertThat(entities).hasSize(1);
146+
Entity entity = entities.get(0);
147+
assertThat(entity.getField1()).isNotNull();
148+
assertThat(entity.getField2()).isNull();
149+
assertThat(entity.getField3()).isNotNull();
150+
}
151+
152+
@Test // #1659
153+
@DisplayName("should only return included fields from SourceFilter on search")
154+
void shouldOnlyReturnIncludedFieldsFromSourceFilterOnSearch() {
155+
156+
Query query = Query.findAll();
157+
query.addSourceFilter(new SourceFilter() {
158+
@Override
159+
public String[] getIncludes() {
160+
return new String[] { "field2" };
161+
}
162+
163+
@Override
164+
public String[] getExcludes() {
165+
return new String[] {};
166+
}
167+
});
168+
169+
SearchHits<Entity> entities = operations.search(query, Entity.class);
170+
171+
assertThat(entities).hasSize(1);
172+
Entity entity = entities.getSearchHit(0).getContent();
173+
assertThat(entity.getField1()).isNull();
174+
assertThat(entity.getField2()).isNotNull();
175+
assertThat(entity.getField3()).isNull();
176+
}
177+
178+
@Test // #1659
179+
@DisplayName("should only return included fields from SourceFilter on multiget")
180+
void shouldOnlyReturnIncludedFieldsFromSourceFilterOnMultiGet() {
181+
182+
Query query = new NativeSearchQueryBuilder().withIds(Collections.singleton("42")).build();
183+
query.addSourceFilter(new SourceFilter() {
184+
@Override
185+
public String[] getIncludes() {
186+
return new String[] { "field2" };
187+
}
188+
189+
@Override
190+
public String[] getExcludes() {
191+
return new String[] {};
192+
}
193+
});
194+
195+
List<Entity> entities = operations.multiGet(query, Entity.class);
196+
197+
assertThat(entities).hasSize(1);
198+
Entity entity = entities.get(0);
199+
assertThat(entity.getField1()).isNull();
200+
assertThat(entity.getField2()).isNotNull();
201+
assertThat(entity.getField3()).isNull();
202+
}
203+
204+
@Data
205+
@Builder
206+
@NoArgsConstructor
207+
@AllArgsConstructor
208+
@Document(indexName = "sourcefilter-tests")
209+
public static class Entity {
210+
@Id private String id;
211+
@Field(type = FieldType.Text) private String field1;
212+
@Field(type = FieldType.Text) private String field2;
213+
@Field(type = FieldType.Text) private String field3;
214+
}
215+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core;
17+
18+
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
19+
import org.springframework.test.context.ContextConfiguration;
20+
21+
/**
22+
* @author Peter-Josef Meisch
23+
*/
24+
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class })
25+
public class SourceFilterIntegrationTransportTests extends SourceFilterIntegrationTests {}

0 commit comments

Comments
 (0)