@@ -34,6 +34,11 @@ public Query convertEqualityComparison(Value leftValue, Value rightValue) {
3434 Object value = leftField != null ? extractValue (rightValue ) : extractValue (leftValue );
3535
3636 if (fieldName != null && value != null ) {
37+ // Check if this is an SME wildcard field
38+ if (isSmeWildcardField (fieldName )) {
39+ return createSmeWildcardQuery (fieldName , value .toString ());
40+ }
41+
3742 return QueryBuilders .term ()
3843 .field (fieldName )
3944 .value (convertToFieldValue (value ))
@@ -60,6 +65,13 @@ public Query convertInequalityComparison(Value leftValue, Value rightValue) {
6065 Object value = leftField != null ? extractValue (rightValue ) : extractValue (leftValue );
6166
6267 if (fieldName != null && value != null ) {
68+ // Check if this is an SME wildcard field
69+ if (isSmeWildcardField (fieldName )) {
70+ return QueryBuilders .bool ()
71+ .mustNot (createSmeWildcardQuery (fieldName , value .toString ()))
72+ .build ()._toQuery ();
73+ }
74+
6375 return QueryBuilders .bool ()
6476 .mustNot (QueryBuilders .term ()
6577 .field (fieldName )
@@ -98,6 +110,11 @@ public Query convertRangeComparison(Value leftValue,
98110 Object rawValue = leftField != null ? extractValue (rightValue ) : extractValue (leftValue );
99111
100112 if (fieldName != null && rawValue != null ) {
113+ // Check if this is an SME wildcard field
114+ if (isSmeWildcardField (fieldName )) {
115+ return createSmeWildcardRangeQuery (fieldName , rawValue , operator );
116+ }
117+
101118 JsonData value = JsonData .of (rawValue ); // works for all scalar types
102119
103120 return QueryBuilders .range (r -> r
@@ -137,6 +154,11 @@ public Query convertStringComparison(StringValue leftValue, StringValue rightVal
137154 String value = leftField != null ? extractStringValue (rightValue ) : extractStringValue (leftValue );
138155
139156 if (fieldName != null && value != null ) {
157+ // Check if this is an SME wildcard field
158+ if (isSmeWildcardField (fieldName )) {
159+ return createSmeWildcardStringQuery (fieldName , value , operation );
160+ }
161+
140162 switch (operation ) {
141163 case "contains" :
142164 return QueryBuilders .wildcard ()
@@ -375,6 +397,8 @@ private String convertModelFieldToElasticField(String modelField) {
375397 result = modelField .replace ("$sm#" , "" );
376398 } else if (modelField .startsWith ("$sme" )) {
377399 result = modelField .replaceFirst ("\\ $sme(?:\\ .[^#]*)?#" , "" );
400+ // Mark as SME field for wildcard handling
401+ result = "SME_WILDCARD:" + result ;
378402 } else if (modelField .startsWith ("$cd#" )) {
379403 result = modelField .replace ("$cd#" , "" );
380404 } else if (modelField .startsWith ("$aasdesc#" )) {
@@ -615,4 +639,135 @@ private String generateCastScript(String fieldExpression, String castType) {
615639 return fieldExpression ; // No casting applied
616640 }
617641 }
642+
643+ /**
644+ * Checks if a field is an SME wildcard field that needs special handling
645+ */
646+ private boolean isSmeWildcardField (String fieldName ) {
647+ return fieldName != null && fieldName .startsWith ("SME_WILDCARD:" );
648+ }
649+
650+ /**
651+ * Extracts the actual field name from an SME wildcard field
652+ */
653+ private String extractSmeFieldName (String wildcardField ) {
654+ if (wildcardField .startsWith ("SME_WILDCARD:" )) {
655+ return wildcardField .substring ("SME_WILDCARD:" .length ());
656+ }
657+ return wildcardField ;
658+ }
659+
660+ /**
661+ * Creates a wildcard query using QueryBuilders.queryString for SME fields at any nesting level
662+ */
663+ private Query createSmeWildcardQuery (String wildcardField , String value ) {
664+ String fieldName = extractSmeFieldName (wildcardField );
665+
666+ // Add .keyword suffix for string fields that need exact matching
667+ String searchField = fieldName ;
668+
669+ // Create a wildcard pattern that matches the field at any nesting level
670+ // Pattern: submodelElements.*{fieldName}:{value} OR submodelElements.*.smcChildren.*{fieldName}:{value}
671+ String queryPattern = "submodelElements.*." +searchField ;
672+
673+ return QueryBuilders .queryString (q -> q
674+ .query (value )
675+ .fields (queryPattern )
676+ );
677+ }
678+
679+ /**
680+ * Creates a wildcard string query using QueryBuilders.queryString for SME fields at any nesting level
681+ */
682+ private Query createSmeWildcardStringQuery (String wildcardField , String value , String operation ) {
683+ String fieldName = extractSmeFieldName (wildcardField );
684+
685+ // Add .keyword suffix for string fields that need exact matching
686+ String searchField = fieldName ;
687+ if (isStringField (fieldName )) {
688+ searchField = fieldName + ".keyword" ;
689+ }
690+
691+ String searchValue ;
692+ switch (operation ) {
693+ case "contains" :
694+ searchValue = "*" + escapeQueryString (value ) + "*" ;
695+ break ;
696+ case "starts-with" :
697+ searchValue = escapeQueryString (value ) + "*" ;
698+ break ;
699+ case "ends-with" :
700+ searchValue = "*" + escapeQueryString (value );
701+ break ;
702+ case "regex" :
703+ // For regex, use the value as-is (queryString supports regex)
704+ searchValue = value ;
705+ break ;
706+ default :
707+ searchValue = escapeQueryString (value );
708+ break ;
709+ }
710+
711+ // Create a wildcard pattern that matches the field at any nesting level
712+ String queryPattern = "submodelElements.*." +searchField ;
713+
714+ return QueryBuilders .queryString (q -> q
715+ .query (value )
716+ .fields (queryPattern )
717+ );
718+ }
719+
720+ /**
721+ * Creates a wildcard range query using QueryBuilders.queryString for SME fields at any nesting level
722+ */
723+ private Query createSmeWildcardRangeQuery (String wildcardField , Object value , String operator ) {
724+ String fieldName = extractSmeFieldName (wildcardField );
725+
726+ // Add .keyword suffix for string fields that need exact matching
727+ String searchField = fieldName ;
728+ if (isStringField (fieldName )) {
729+ searchField = fieldName + ".keyword" ;
730+ }
731+
732+ String rangeOperator ;
733+ switch (operator ) {
734+ case "gt" : rangeOperator = ">" ; break ;
735+ case "gte" : rangeOperator = ">=" ; break ;
736+ case "lt" : rangeOperator = "<" ; break ;
737+ case "lte" : rangeOperator = "<=" ; break ;
738+ default : throw new IllegalArgumentException ("Unsupported range operator: " + operator );
739+ }
740+
741+ // Create a wildcard pattern that matches the field at any nesting level with range comparison
742+ String queryPattern = "submodelElements.*." +searchField ;
743+
744+ return QueryBuilders .queryString (q -> q
745+ .query (value .toString ())
746+ .fields (queryPattern )
747+ );
748+ }
749+
750+ /**
751+ * Escapes special characters for Elasticsearch query string queries
752+ */
753+ private String escapeQueryString (String value ) {
754+ // Escape special query string characters
755+ return value .replace ("\\ " , "\\ \\ " )
756+ .replace ("\" " , "\\ \" " )
757+ .replace ("+" , "\\ +" )
758+ .replace ("-" , "\\ -" )
759+ .replace ("=" , "\\ =" )
760+ .replace ("&&" , "\\ &&" )
761+ .replace ("||" , "\\ ||" )
762+ .replace ("!" , "\\ !" )
763+ .replace ("(" , "\\ (" )
764+ .replace (")" , "\\ )" )
765+ .replace ("{" , "\\ {" )
766+ .replace ("}" , "\\ }" )
767+ .replace ("[" , "\\ [" )
768+ .replace ("]" , "\\ ]" )
769+ .replace ("^" , "\\ ^" )
770+ .replace ("~" , "\\ ~" )
771+ .replace (":" , "\\ :" );
772+ }
618773}
0 commit comments