Skip to content

Commit 7c544f8

Browse files
committed
Adapts SM Repo QL
1 parent 4a282e4 commit 7c544f8

12 files changed

Lines changed: 171 additions & 129 deletions

File tree

basyx.aasrepository/basyx.aasrepository-feature-search/README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
## Risks
4848

4949
* Errors can be catched in the decorated functions (transactional)
50+
* Security needs to be considered -> Just query id of elements and then go to the MongoDB
5051

5152

5253
## TODOS
@@ -61,7 +62,10 @@
6162
* [x] Configurable Index Names
6263
* [x] Implement Casting Operators
6364
* [x] Unit Test
64-
* [x] !Priority! Duplicate search module for the missing components
65-
* [ ] In SearchSubmodelRepository, ensure that the index does not disable certain fields in ensureIndexExists
65+
* [x] !Priority! Duplicate search module for the missing components
6666
* [ ] Integration Tests
67-
* [ ] Validate Expected Queries (unformatted ones) -> Depends on other components to utilize the AASQL
67+
* [ ] Validate Expected Queries (unformatted ones) -> Depends on other components to utilize the AASQL
68+
* [ ] Make the following SME Props queryable: value, valueType, semanticId, idShort
69+
* [ ] Make other Components (AAS,CD) also fetch Data from MongoDB not ES
70+
* [ ] Note down that comparison operators don't work for SME filtering in SM Repo without idShortPath
71+
* [ ] Handle Search Queries with SML (Indices)

basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java

Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -69,68 +69,5 @@ public SearchRequest buildSearchRequest(AASQuery customQuery, String indexName,
6969

7070
return searchBuilder.build();
7171
}
72-
73-
/**
74-
* Builds ElasticSearch SearchRequest with custom source includes
75-
*
76-
* @param customQuery The custom query to convert
77-
* @param indexName The ElasticSearch index name to search
78-
* @param sourceIncludes List of fields to include in the response
79-
* @return ElasticSearch SearchRequest
80-
*/
81-
public SearchRequest buildSearchRequestWithSources(AASQuery customQuery, String indexName, List<String> sourceIncludes) {
82-
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = queryConverter.convert(customQuery);
83-
84-
SearchRequest.Builder searchBuilder = new SearchRequest.Builder()
85-
.index(indexName)
86-
.query(esQuery);
87-
88-
if (sourceIncludes != null && !sourceIncludes.isEmpty()) {
89-
searchBuilder.source(SourceConfig.of(s -> s
90-
.filter(SourceFilter.of(f -> f
91-
.includes(sourceIncludes)
92-
))
93-
));
94-
}
95-
96-
return searchBuilder.build();
97-
}
98-
99-
/**
100-
* Builds ElasticSearch SearchRequest with custom source includes and excludes
101-
*
102-
* @param customQuery The custom query to convert
103-
* @param indexName The ElasticSearch index name to search
104-
* @param sourceIncludes List of fields to include in the response
105-
* @param sourceExcludes List of fields to exclude from the response
106-
* @return ElasticSearch SearchRequest
107-
*/
108-
public SearchRequest buildSearchRequestWithSourceFilters(AASQuery customQuery, String indexName,
109-
List<String> sourceIncludes, List<String> sourceExcludes) {
110-
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = queryConverter.convert(customQuery);
111-
112-
SearchRequest.Builder searchBuilder = new SearchRequest.Builder()
113-
.index(indexName)
114-
.query(esQuery);
115-
116-
if ((sourceIncludes != null && !sourceIncludes.isEmpty()) ||
117-
(sourceExcludes != null && !sourceExcludes.isEmpty())) {
118-
119-
SourceFilter.Builder filterBuilder = new SourceFilter.Builder();
120-
121-
if (sourceIncludes != null && !sourceIncludes.isEmpty()) {
122-
filterBuilder.includes(sourceIncludes);
123-
}
124-
125-
if (sourceExcludes != null && !sourceExcludes.isEmpty()) {
126-
filterBuilder.excludes(sourceExcludes);
127-
}
128-
129-
searchBuilder.source(SourceConfig.of(s -> s
130-
.filter(filterBuilder.build())
131-
));
132-
}
133-
134-
return searchBuilder.build();
135-
}
136-
}
72+
73+
}

basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,19 @@ private String convertModelFieldToElasticField(String modelField) {
396396
} else if (modelField.startsWith("$sm#")) {
397397
result = modelField.replace("$sm#", "");
398398
} else if (modelField.startsWith("$sme")) {
399+
boolean hasIdShortPath = !result.startsWith("$sme#");
400+
String idShortPath = "";
401+
if (hasIdShortPath) {
402+
int indexOfRaute = result.indexOf("#");
403+
idShortPath = result.substring(5, indexOfRaute);
404+
}
399405
result = modelField.replaceFirst("\\$sme(?:\\.[^#]*)?#", "");
400406
// Mark as SME field for wildcard handling
401-
result = "SME_WILDCARD:" + result;
407+
if (!hasIdShortPath) {
408+
result = "SME_WILDCARD:" + result;
409+
} else {
410+
result = "submodelElements." + idShortPath+ "." + result;
411+
}
402412
} else if (modelField.startsWith("$cd#")) {
403413
result = modelField.replace("$cd#", "");
404414
} else if (modelField.startsWith("$aasdesc#")) {
@@ -668,7 +678,7 @@ private Query createSmeWildcardQuery(String wildcardField, String value) {
668678

669679
// Create a wildcard pattern that matches the field at any nesting level
670680
// Pattern: submodelElements.*{fieldName}:{value} OR submodelElements.*.smcChildren.*{fieldName}:{value}
671-
String queryPattern = "submodelElements.*."+searchField;
681+
String queryPattern = "*"+searchField;
672682

673683
return QueryBuilders.queryString(q -> q
674684
.query(value)
@@ -682,25 +692,20 @@ private Query createSmeWildcardQuery(String wildcardField, String value) {
682692
private Query createSmeWildcardStringQuery(String wildcardField, String value, String operation) {
683693
String fieldName = extractSmeFieldName(wildcardField);
684694

685-
// Add .keyword suffix for string fields that need exact matching
686695
String searchField = fieldName;
687-
if (isStringField(fieldName)) {
688-
searchField = fieldName + ".keyword";
689-
}
690-
696+
691697
String searchValue;
692698
switch (operation) {
693699
case "contains":
694-
searchValue = "*" + escapeQueryString(value) + "*";
700+
searchValue = "*" + value + "*";
695701
break;
696702
case "starts-with":
697-
searchValue = escapeQueryString(value) + "*";
703+
searchValue = value + "*";
698704
break;
699705
case "ends-with":
700-
searchValue = "*" + escapeQueryString(value);
706+
searchValue = "*" + value;
701707
break;
702708
case "regex":
703-
// For regex, use the value as-is (queryString supports regex)
704709
searchValue = value;
705710
break;
706711
default:
@@ -709,10 +714,10 @@ private Query createSmeWildcardStringQuery(String wildcardField, String value, S
709714
}
710715

711716
// Create a wildcard pattern that matches the field at any nesting level
712-
String queryPattern = "submodelElements.*."+searchField;
717+
String queryPattern = "*"+searchField;
713718

714719
return QueryBuilders.queryString(q -> q
715-
.query(value)
720+
.query(searchValue)
716721
.fields(queryPattern)
717722
);
718723
}
@@ -723,12 +728,8 @@ private Query createSmeWildcardStringQuery(String wildcardField, String value, S
723728
private Query createSmeWildcardRangeQuery(String wildcardField, Object value, String operator) {
724729
String fieldName = extractSmeFieldName(wildcardField);
725730

726-
// Add .keyword suffix for string fields that need exact matching
727731
String searchField = fieldName;
728-
if (isStringField(fieldName)) {
729-
searchField = fieldName + ".keyword";
730-
}
731-
732+
732733
String rangeOperator;
733734
switch (operator) {
734735
case "gt": rangeOperator = ">"; break;
@@ -739,11 +740,13 @@ private Query createSmeWildcardRangeQuery(String wildcardField, Object value, St
739740
}
740741

741742
// Create a wildcard pattern that matches the field at any nesting level with range comparison
742-
String queryPattern = "submodelElements.*."+searchField;
743+
String queryPattern = "*"+searchField;
744+
745+
// Construct the range query string with proper syntax: fieldPattern:(>=value)
746+
String rangeQuery = queryPattern + ":(" + rangeOperator + value.toString() + ")";
743747

744748
return QueryBuilders.queryString(q -> q
745-
.query(value.toString())
746-
.fields(queryPattern)
749+
.query(rangeQuery)
747750
);
748751
}
749752

basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,8 @@ private Object filterEmptyArrays(Object obj) {
121121
}
122122

123123
private QueryResponse getQueryResponse(AASQuery query, List<Object> objectHits, String nextCursor) {
124-
QueryResult queryResult = new QueryResult(objectHits);
125124
QueryPaging queryPaging = new QueryPaging(nextCursor, getResultType(query));
126-
QueryResponse queryResponse = new QueryResponse(queryPaging, queryResult);
125+
QueryResponse queryResponse = new QueryResponse(queryPaging, objectHits);
127126
return queryResponse;
128127
}
129128

basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package org.eclipse.digitaltwin.basyx.querycore.query.model;
22

3+
import java.util.List;
4+
35
public class QueryResponse {
46

57
public QueryPaging paging_metadata;
6-
public QueryResult result;
8+
public List<Object> result;
79

8-
public QueryResponse(QueryPaging paging_metadata, QueryResult result) {
10+
public QueryResponse(QueryPaging paging_metadata, List<Object> result) {
911
this.paging_metadata = paging_metadata;
1012
this.result = result;
1113
}

basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public class AASQueryConverterTest {
5252
put("match_eq_eq_field_strval.json", "expected_match_eq_eq_field_strval.json");
5353
put("match_specific_asset_ids.json", "expected_match_specific_asset_ids.json");
5454
put("regex_field_strval.json", "expected_regex_field_strval.json");
55+
put("starts-with_field_strval.json", "expected_starts-with_field_strval.json");
56+
put("ends-with_field_strval.json", "expected_ends-with_field_strval.json");
5557
}};
5658

5759
@Test
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$condition": {
3+
"$starts-with": [
4+
{ "$field": "$aas#assetInformation.assetKind" },
5+
{ "$strVal": "NST" }
6+
]
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$condition": {
3+
"$starts-with": [
4+
{ "$field": "$aas#assetInformation.assetKind" },
5+
{ "$strVal": "NST" }
6+
]
7+
}
8+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"wildcard":{"assetInformation.assetKind.keyword":{"value":"*NST"}}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"wildcard":{"assetInformation.assetKind.keyword":{"value":"NST*"}}}

0 commit comments

Comments
 (0)