1+ /*
2+ * Copyright (c) 2023 Contributors to the Eclipse Foundation
3+ * All rights reserved. This program and the accompanying materials
4+ * are made available under the terms of the Eclipse Public License v1.0
5+ * and Apache License v2.0 which accompanies this distribution.
6+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
7+ * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
8+ *
9+ * You may elect to redistribute this code under either of these licenses.
10+ *
11+ * Contributors:
12+ *
13+ * Maximillian Arruda
14+ *
15+ */
16+
17+ package org .eclipse .jnosql .databases .elasticsearch .communication ;
18+
19+ import co .elastic .clients .elasticsearch ._types .mapping .KeywordProperty ;
20+ import co .elastic .clients .elasticsearch ._types .query_dsl .Query ;
21+ import co .elastic .clients .elasticsearch .indices .PutMappingRequest ;
22+ import co .elastic .clients .elasticsearch .indices .get_mapping .IndexMappingRecord ;
23+ import co .elastic .clients .util .ObjectBuilder ;
24+ import jakarta .nosql .Column ;
25+ import jakarta .nosql .Id ;
26+ import org .awaitility .Awaitility ;
27+ import org .eclipse .jnosql .communication .document .DocumentCondition ;
28+ import org .eclipse .jnosql .communication .document .DocumentQuery ;
29+ import org .eclipse .jnosql .mapping .config .MappingConfigurations ;
30+ import org .junit .jupiter .api .AfterEach ;
31+ import org .junit .jupiter .api .BeforeEach ;
32+ import org .junit .jupiter .api .Test ;
33+ import org .junit .jupiter .api .condition .EnabledIfSystemProperty ;
34+
35+ import java .io .IOException ;
36+ import java .util .HashMap ;
37+ import java .util .Map ;
38+ import java .util .Set ;
39+ import java .util .function .Function ;
40+
41+ import static java .util .concurrent .TimeUnit .MILLISECONDS ;
42+ import static java .util .concurrent .TimeUnit .SECONDS ;
43+ import static org .assertj .core .api .SoftAssertions .assertSoftly ;
44+ import static org .eclipse .jnosql .communication .driver .IntegrationTest .MATCHES ;
45+ import static org .eclipse .jnosql .communication .driver .IntegrationTest .NAMED ;
46+
47+ @ EnabledIfSystemProperty (named = NAMED , matches = MATCHES )
48+ class QueryConverterTest {
49+
50+ private static final String INDEX = QueryConverterTest .class .getSimpleName ().toLowerCase ();
51+
52+ static {
53+ System .setProperty (ElasticsearchConfigurations .HOST .get () + ".1" , DocumentDatabase .INSTANCE .host ());
54+ System .setProperty (MappingConfigurations .DOCUMENT_DATABASE .get (), INDEX );
55+ Awaitility .setDefaultPollDelay (100 , MILLISECONDS );
56+ Awaitility .setDefaultTimeout (2L , SECONDS );
57+ }
58+
59+ private DocumentDatabase .ElasticsearchClientAutoClosable elasticsearch ;
60+
61+ @ BeforeEach
62+ void setUp () throws IOException {
63+ this .elasticsearch = DocumentDatabase .INSTANCE .newElasticsearchClient ();
64+ clearDatabase ();
65+ }
66+
67+ @ AfterEach
68+ void clearDatabase () throws IOException {
69+ DocumentDatabase .clearDatabase (INDEX );
70+ }
71+
72+
73+ public static record Book (
74+ @ Id
75+ String id ,
76+ @ Column
77+ String name ,
78+ @ Column
79+ Integer edition ) {
80+ }
81+
82+ final Book effectiveJava = new Book (
83+ "6eaeafcd-3e6a-4ef6-9f5f-a1b33677850d" ,
84+ "Effective Java" ,
85+ 1 );
86+
87+ @ Test
88+ void testSupportTermQuery () throws Exception {
89+
90+ Map <String , Object > map = new HashMap <>();
91+ map .put ("name" , effectiveJava .name ());
92+ map .put ("edition" , effectiveJava .edition );
93+ map .put ("doc2" , Map .of ("data1" , "teste" , "data2" , 333L ));
94+ map .put ("@entity" , Book .class .getSimpleName ());
95+
96+ insertData (map );
97+
98+ IndexMappingRecord indexMappingRecordWithoutKeywordAttributes = elasticsearch .client ().indices ().getMapping (b -> b .index (INDEX )).get (INDEX );
99+
100+ assertSoftly (softly -> {
101+
102+ softly .assertThat (indexMappingRecordWithoutKeywordAttributes )
103+ .as ("indexMappingRecordWithoutKeywordAttributes wasn't provided" )
104+ .isNotNull ();
105+
106+ map .keySet ().forEach (key -> {
107+ softly .assertThat (QueryConverter .supportTermQuery (indexMappingRecordWithoutKeywordAttributes , key ))
108+ .as ("%s attribute should not support TermQuery" .formatted (key ))
109+ .isFalse ();
110+ });
111+
112+ Set .of ("doc2.data1" , "doc2.data2" ).forEach (key -> {
113+ softly .assertThat (QueryConverter .supportTermQuery (indexMappingRecordWithoutKeywordAttributes , key ))
114+ .as ("%s attribute should not support TermQuery" .formatted (key ))
115+ .isFalse ();
116+ });
117+
118+ });
119+
120+ clearDatabase ();
121+ recreateIndexWithKeywordFields (m ->
122+ m .index (INDEX )
123+ .properties ("@entity" , f -> f .keyword (KeywordProperty .of (k -> k )))
124+ .properties ("doc2" , f -> f .object (o -> o .properties ("data1" , p -> p .keyword (KeywordProperty .of (k -> k )))))
125+ );
126+
127+ insertData (map );
128+
129+ IndexMappingRecord indexMappingRecordWithKeywordAttributes = elasticsearch .client ().indices ().getMapping (b -> b .index (INDEX )).get (INDEX );
130+
131+ assertSoftly (softly -> {
132+
133+ softly .assertThat (indexMappingRecordWithKeywordAttributes )
134+ .as ("indexMappingRecordWithKeywordAttributes wasn't provided" )
135+ .isNotNull ();
136+
137+ map .keySet ().stream ()
138+ .filter (anObject -> !"@entity" .equals (anObject ))
139+ .forEach (key -> {
140+ softly .assertThat (QueryConverter .supportTermQuery (indexMappingRecordWithKeywordAttributes , key ))
141+ .as ("%s attribute should not support TermQuery" .formatted (key ))
142+ .isFalse ();
143+
144+ });
145+
146+
147+ softly .assertThat (QueryConverter .supportTermQuery (indexMappingRecordWithKeywordAttributes , "@entity" ))
148+ .as ("%s attribute should support TermQuery" .formatted ("@entity" ))
149+ .isTrue ();
150+
151+
152+ softly .assertThat (QueryConverter .supportTermQuery (indexMappingRecordWithKeywordAttributes , "doc2.data1" ))
153+ .as ("%s attribute should not support TermQuery" .formatted ("doc2.data1" ))
154+ .isTrue ();
155+
156+ softly .assertThat (QueryConverter .supportTermQuery (indexMappingRecordWithKeywordAttributes , "doc2.data2" ))
157+ .as ("%s attribute should not support TermQuery" .formatted ("doc2.data2" ))
158+ .isFalse ();
159+
160+ });
161+
162+ }
163+
164+ @ Test
165+ void testSelect () throws Exception {
166+
167+ Map <String , Object > map = new HashMap <>();
168+ map .put ("_id" , effectiveJava .id ());
169+ map .put ("name" , effectiveJava .name ());
170+ map .put ("edition" , effectiveJava .edition ());
171+ map .put ("doc2" , Map .of ("data1" , "teste" , "data2" , 333L ));
172+ map .put ("@entity" , Book .class .getSimpleName ());
173+
174+ insertData (map );
175+
176+ DocumentQuery query = DocumentQuery .builder ()
177+ .select ()
178+ .from ("Book" )
179+ .where (DocumentCondition .eq ("_id" , effectiveJava .id ())).build ();
180+
181+ QueryConverterResult selectWithoutKeywordFields = QueryConverter .select (elasticsearch .client (), INDEX , query );
182+
183+ assertSoftly (softly -> {
184+ softly .assertThat (selectWithoutKeywordFields )
185+ .as ("QueryConverter.select() should returns a non-null object" )
186+ .isNotNull ();
187+
188+ Query _query = selectWithoutKeywordFields .getStatement ().build ();
189+ softly .assertThat (_query ._kind ())
190+ .as ("QueryConverterResult's statement should be a %s query" .formatted (Query .Kind .Bool ))
191+ .isEqualTo (Query .Kind .Bool );
192+
193+ softly .assertThat (_query .bool ().must ())
194+ .as ("QueryConverterResult's statement should be a %s query with 2 conditions" .formatted (Query .Kind .Bool ))
195+ .hasSize (2 );
196+
197+ softly .assertThat (_query .bool ().must ().get (0 )._kind ())
198+ .as ("first condition of QueryConverterResult's statement should be a %s query" .formatted (Query .Kind .Match ))
199+ .isEqualTo (Query .Kind .Match );
200+
201+ softly .assertThat (_query .bool ().must ().get (1 )._kind ())
202+ .as ("second condition of QueryConverterResult's statement should be a %s query" .formatted (Query .Kind .Match ))
203+ .isEqualTo (Query .Kind .Match );
204+
205+ });
206+
207+ System .out .println (selectWithoutKeywordFields );
208+
209+ clearDatabase ();
210+ recreateIndexWithKeywordFields (m ->
211+ m .index (INDEX )
212+ .properties ("@entity" , f -> f .keyword (KeywordProperty .of (k -> k )))
213+ .properties ("name" , f -> f .keyword (KeywordProperty .of (k -> k )))
214+ .properties ("edition" , f -> f .keyword (KeywordProperty .of (k -> k )))
215+ .properties ("doc2" , f -> f .object (o -> o .properties ("data1" , p -> p .keyword (KeywordProperty .of (k -> k )))))
216+ );
217+
218+ insertData (map );
219+
220+ DocumentQuery query2 = DocumentQuery .builder ()
221+ .select ()
222+ .from ("Book" )
223+ .where (DocumentCondition .eq ("_id" , effectiveJava .id ())
224+ .and (DocumentCondition .eq ("doc2.data1" , "teste" )
225+ .and (DocumentCondition .eq ("doc2.data2" , 222L )))).build ();
226+
227+ QueryConverterResult selectWithKeywordFields = QueryConverter .select (elasticsearch .client (), INDEX , query2 );
228+
229+ assertSoftly (softly -> {
230+ softly .assertThat (selectWithKeywordFields )
231+ .as ("QueryConverter.select() should returns a non-null object" )
232+ .isNotNull ();
233+
234+ Query builtQuery = selectWithKeywordFields .getStatement ().build ();
235+
236+ softly .assertThat (builtQuery ._kind ())
237+ .as ("QueryConverterResult's statement should be a %s query" .formatted (Query .Kind .Bool ))
238+ .isEqualTo (Query .Kind .Bool );
239+
240+ softly .assertThat (builtQuery .bool ().must ())
241+ .as ("QueryConverterResult's statement should be a %s query" .formatted (Query .Kind .Bool ))
242+ .hasSize (2 );
243+
244+ Query firstCondition = builtQuery .bool ().must ().get (0 );
245+ softly .assertThat (firstCondition ._kind ())
246+ .as ("QueryConverterResult's statement's first condition should be a %s query" .formatted (Query .Kind .Term ))
247+ .isEqualTo (Query .Kind .Term );
248+
249+ Query secondCondition = builtQuery .bool ().must ().get (1 );
250+ softly .assertThat (secondCondition ._kind ())
251+ .as ("QueryConverterResult's statement's second condition should be a %s query" .formatted (Query .Kind .Bool ))
252+ .isEqualTo (Query .Kind .Bool );
253+
254+ softly .assertThat (secondCondition .bool ().must ())
255+ .as ("QueryConverterResult's statement should be a %s query" .formatted (Query .Kind .Term ))
256+ .hasSize (2 );
257+
258+ Query firstSubConditionOfSecondCondition = secondCondition .bool ().must ().get (0 );
259+ softly .assertThat (firstSubConditionOfSecondCondition ._kind ())
260+ .as ("first sub-condition of QueryConverterResult's statement's second condition should be a %s query" .formatted (Query .Kind .Match ))
261+ .isEqualTo (Query .Kind .Match );
262+
263+ Query secondSubConditionOfSecondCondition = secondCondition .bool ().must ().get (1 );
264+ softly .assertThat (secondSubConditionOfSecondCondition ._kind ())
265+ .as ("second sub-condition of QueryConverterResult's statement's second condition should be a %s query" .formatted (Query .Kind .Bool ))
266+ .isEqualTo (Query .Kind .Bool );
267+
268+ softly .assertThat (secondSubConditionOfSecondCondition .bool ().must ())
269+ .as ("second sub-condition of QueryConverterResult's statement's second condition should has wrong size" )
270+ .hasSize (2 );
271+
272+
273+ Query firstSubSubConditionOfSecondCondition = secondSubConditionOfSecondCondition .bool ().must ().get (0 );
274+ softly .assertThat (firstSubSubConditionOfSecondCondition ._kind ())
275+ .as ("first sub-condition of the second sub-condition of QueryConverterResult's statement's second condition should be a %s query" .formatted (Query .Kind .Term ))
276+ .isEqualTo (Query .Kind .Term );
277+
278+ Query secondSubSubConditionOfSecondCondition = secondSubConditionOfSecondCondition .bool ().must ().get (1 );
279+ softly .assertThat (secondSubSubConditionOfSecondCondition ._kind ())
280+ .as ("second sub-condition of the second sub-condition of QueryConverterResult's statement's second condition should be a %s query" .formatted (Query .Kind .Match ))
281+ .isEqualTo (Query .Kind .Match );
282+
283+ });
284+
285+ }
286+
287+ private void recreateIndexWithKeywordFields (Function <PutMappingRequest .Builder , ObjectBuilder <PutMappingRequest >> fn ) {
288+ DocumentDatabase .createDatabase (INDEX );
289+ DocumentDatabase .updateMapping (INDEX , fn );
290+ }
291+
292+ private void insertData (Map <String , Object > map ) throws IOException {
293+ DocumentDatabase .insertData (INDEX , map );
294+ }
295+ }
0 commit comments