This is an automated email from the ASF dual-hosted git repository.

thomasm pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 22fc672f00 OAK-11555 Elastic: support dot in property and function 
names (#2145)
22fc672f00 is described below

commit 22fc672f0020db338f957a0b1a4ea2707782a730
Author: Thomas Mueller <[email protected]>
AuthorDate: Thu Mar 13 08:29:35 2025 +0100

    OAK-11555 Elastic: support dot in property and function names (#2145)
    
    * OAK-11555 Elastic: support dot in property and function names
    
    * OAK-11555 Elastic: support dot in property and function names
    
    * OAK-11555 Elastic: support dot in property and function names
    
    * Update 
oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticIndexUtils.java
    
    Co-authored-by: Nuno Santos <[email protected]>
    
    * OAK-11555 Elastic: support dot in property and function names
    
    * OAK-11555 Elastic: support dot in property and function names
    
    * OAK-11555 Elastic: support dot in property and function names
    
    * OAK-11555 Elastic: support dot in property and function names
    
    ---------
    
    Co-authored-by: Nuno Santos <[email protected]>
---
 .../index/elastic/ElasticIndexDefinition.java      |  14 +--
 .../index/elastic/ElasticIndexStatistics.java      |   5 +-
 .../index/elastic/index/ElasticDocument.java       |   8 +-
 .../index/elastic/index/ElasticDocumentMaker.java  |  14 ++-
 .../index/elastic/index/ElasticIndexHelper.java    |  20 +--
 .../index/elastic/query/ElasticRequestHandler.java |  33 +++--
 .../facets/ElasticSecureFacetAsyncProvider.java    |   8 +-
 .../ElasticStatisticalFacetAsyncProvider.java      |   8 +-
 .../index/elastic/util/ElasticIndexUtils.java      |  70 ++++++++++-
 .../index/elastic/ElasticConnectionRule.java       |   2 +-
 .../plugins/index/elastic/ElasticContentTest.java  |   9 +-
 .../index/elastic/ElasticIndexAggregationTest.java |  82 ++++++++++++
 .../elastic/ElasticIndexSuggestionCommonTest.java  |   2 +-
 .../index/elastic/ElasticPropertyIndexTest.java    |  64 +++++++++-
 .../elastic/ElasticRegexPropertyIndexTest.java     |  16 ++-
 .../index/elastic/ElasticSimilarQueryTest.java     |   8 +-
 ...cDocumentMakerLargeStringPropertiesLogTest.java |   2 +-
 .../elastic/index/ElasticIndexHelperTest.java      |   7 +-
 .../index/elastic/util/ElasticIndexUtilsTest.java  | 139 +++++++++++++++++++++
 .../oak/plugins/index/IndexPlannerCommonTest.java  |   2 +-
 20 files changed, 450 insertions(+), 63 deletions(-)

diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
index 9d69551d1c..8439feca0e 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
@@ -31,6 +31,7 @@ import java.util.stream.Stream;
 
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.commons.collections.StreamUtils;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
 import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
 import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
@@ -318,19 +319,18 @@ public class ElasticIndexDefinition extends 
IndexDefinition {
      */
     public String getElasticKeyword(String propertyName) {
         List<PropertyDefinition> propertyDefinitions = 
propertiesByName.get(propertyName);
+        String field = ElasticIndexUtils.fieldName(propertyName);
         if (propertyDefinitions == null) {
             // if there are no property definitions we return the default 
keyword name
             // this can happen for properties that were not explicitly defined 
(eg: created with a regex)
             ElasticPropertyDefinition pd = 
getMatchingRegexPropertyDefinition(propertyName);
-            if (pd != null) {
-                if (pd.isFlattened()) {
-                    return FieldNames.FLATTENED_FIELD_PREFIX + pd.nodeName + 
"." + propertyName;
-                }
+            if (pd != null && pd.isFlattened()) {
+                return FieldNames.FLATTENED_FIELD_PREFIX +
+                        ElasticIndexUtils.fieldName(pd.nodeName) + "." + field;
+            } else {
+                return field + ".keyword";
             }
-            return propertyName + ".keyword";
         }
-
-        String field = propertyName;
         // it's ok to look at the first property since we are sure they all 
have the same type
         int type = propertyDefinitions.get(0).getType();
         if (isAnalyzable.apply(type) && isAnalyzed(propertyDefinitions)) {
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexStatistics.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexStatistics.java
index ed52c9b57d..4d64d77ac5 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexStatistics.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexStatistics.java
@@ -25,6 +25,8 @@ import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
 import co.elastic.clients.elasticsearch._types.query_dsl.Query;
+
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import org.apache.jackrabbit.oak.plugins.index.search.IndexStatistics;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -111,8 +113,9 @@ public class ElasticIndexStatistics implements 
IndexStatistics {
      */
     @Override
     public int getDocCountFor(String field) {
+        String elasticField = ElasticIndexUtils.fieldName(field);
         return countCache.getUnchecked(
-                new StatsRequestDescriptor(elasticConnection, 
indexDefinition.getIndexAlias(), field, null)
+                new StatsRequestDescriptor(elasticConnection, 
indexDefinition.getIndexAlias(), elasticField, null)
         );
     }
 
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java
index 3c7dc6f4f3..2f1ee7e26e 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java
@@ -147,9 +147,9 @@ public class ElasticDocument {
         properties.put(fieldName, finalValue);
     }
 
-    void addSimilarityField(String name, Blob value) throws IOException {
+    void addSimilarityField(String fieldName, Blob value) throws IOException {
         byte[] bytes = value.getNewStream().readAllBytes();
-        addProperty(FieldNames.createSimilarityFieldName(name), 
toFloats(bytes));
+        addProperty(FieldNames.createSimilarityFieldName(fieldName), 
toFloats(bytes));
     }
 
     void indexAncestors(String path) {
@@ -160,8 +160,8 @@ public class ElasticDocument {
         addProperty(FieldNames.PATH_DEPTH, depth);
     }
 
-    void addDynamicBoostField(String propName, String value, double boost) {
-        addProperty(propName,
+    void addDynamicBoostField(String fieldName, String value, double boost) {
+        addProperty(fieldName,
                 Map.of(
                         ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE, value,
                         ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST, boost
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMaker.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMaker.java
index 5316cf1127..59c84f9b01 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMaker.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMaker.java
@@ -24,6 +24,7 @@ import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.commons.log.LogSilencer;
 import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.ElasticPropertyDefinition;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import org.apache.jackrabbit.oak.plugins.index.search.Aggregate;
 import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
 import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
@@ -165,16 +166,17 @@ public class ElasticDocumentMaker extends 
FulltextDocumentMaker<ElasticDocument>
     }
 
     @Override
-    protected void indexTypedProperty(ElasticDocument doc, PropertyState 
property, String pname, PropertyDefinition pd, int i) {
+    protected void indexTypedProperty(ElasticDocument doc, PropertyState 
property, String propertyName, PropertyDefinition pd, int i) {
         // Get the Type tag from the defined index definition here - and not 
from the actual persisted property state - this way in case
         // If the actual property value is different from the property type 
defined in the index definition/mapping - this will try to convert the property 
if possible,
         // otherwise will log a warning and not try and add the property to 
index. If we try and index incompatible data types (like String to Date),
         // we would get an exception while indexing the node on elastic search 
and other properties for the node will also don't get indexed. (See OAK-9665).
-        String fieldName = pname;
+        String fieldName = ElasticIndexUtils.fieldName(propertyName);
         if (pd.isRegexp) {
             ElasticPropertyDefinition epd = (ElasticPropertyDefinition) pd;
             if (epd.isFlattened()) {
-                fieldName = FieldNames.FLATTENED_FIELD_PREFIX + epd.nodeName + 
"." + pname;
+                fieldName = FieldNames.FLATTENED_FIELD_PREFIX +
+                        ElasticIndexUtils.fieldName(epd.nodeName) + "." + 
fieldName;
             }
         }
         int tag = pd.getType();
@@ -197,7 +199,7 @@ public class ElasticDocumentMaker extends 
FulltextDocumentMaker<ElasticDocument>
             if (!LOG_SILENCER.silence(LOG_KEY_COULD_NOT_CONVERT_PROPERTY)) {
                 LOG.warn(
                         "[{}] Ignoring property. Could not convert property {} 
(field {}) of type {} to type {} for path {}. Error: {}",
-                        getIndexName(), pname, fieldName,
+                        getIndexName(), propertyName, fieldName,
                         Type.fromTag(property.getType().tag(), false),
                         Type.fromTag(tag, false), path, e.toString());
             }
@@ -252,7 +254,7 @@ public class ElasticDocumentMaker extends 
FulltextDocumentMaker<ElasticDocument>
         if (pd.getSimilaritySearchDenseVectorSize() == blob.length() / 
BLOB_LENGTH_DIVISOR) {
             // see 
https://www.elastic.co/blog/text-similarity-search-with-vectors-in-elasticsearch
             // see 
https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html
-            doc.addSimilarityField(pd.name, blob);
+            doc.addSimilarityField(ElasticIndexUtils.fieldName(pd.name), blob);
         } else {
             if 
(!LOG_SILENCER.silence(LOG_KEY_SIMILARITY_BINARIES_WRONG_DIMENSION)) {
                 LOG.warn("[{}] Ignoring binary property {} for path {}. 
Expected dimension is {} but got {}",
@@ -275,7 +277,7 @@ public class ElasticDocumentMaker extends 
FulltextDocumentMaker<ElasticDocument>
     @Override
     protected boolean indexDynamicBoost(ElasticDocument doc, String parent, 
String nodeName, String token, double boost) {
         if (!token.isEmpty()) {
-            doc.addDynamicBoostField(nodeName, token, boost);
+            doc.addDynamicBoostField(ElasticIndexUtils.fieldName(nodeName), 
token, boost);
             return true;
         }
         return false;
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
index f42432ea0c..cd9ac9f2b3 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
@@ -22,6 +22,7 @@ import co.elastic.clients.json.JsonData;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.ElasticPropertyDefinition;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
 import 
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.IndexingRule;
 import org.apache.jackrabbit.oak.plugins.index.search.PropertyDefinition;
@@ -149,7 +150,8 @@ class ElasticIndexHelper {
         builder.meta("inference", JsonData.of(inferenceDefinition));
 
         if (inferenceDefinition.properties != null) {
-            inferenceDefinition.properties.forEach(p -> 
builder.properties(p.name,
+            inferenceDefinition.properties.forEach(p -> builder.properties(
+                    ElasticIndexUtils.fieldName(p.name),
                     b -> b.object(bo -> bo
                             .properties("value", pb -> pb.denseVector(dv ->
                                             dv.index(true)
@@ -243,13 +245,15 @@ class ElasticIndexHelper {
                 if (epd.isFlattened()) {
                     Property.Builder pBuilder = new Property.Builder();
                     pBuilder.flattened(b2 -> b2.index(true));
-                    builder.properties(FieldNames.FLATTENED_FIELD_PREFIX + 
pd.nodeName, pBuilder.build());
+                    builder.properties(FieldNames.FLATTENED_FIELD_PREFIX +
+                            ElasticIndexUtils.fieldName(pd.nodeName), 
pBuilder.build());
                 }
             }
         }
         for (Map.Entry<String, List<PropertyDefinition>> entry : 
indexDefinition.getPropertiesByName().entrySet()) {
-            final String name = entry.getKey();
-            final List<PropertyDefinition> propertyDefinitions = 
entry.getValue();
+            String propertyName = entry.getKey();
+            String fieldName = ElasticIndexUtils.fieldName(propertyName);
+            List<PropertyDefinition> propertyDefinitions = entry.getValue();
             Type<?> type = null;
             for (PropertyDefinition pd : propertyDefinitions) {
                 type = Type.fromTag(pd.getType(), false);
@@ -280,10 +284,10 @@ class ElasticIndexHelper {
                     pBuilder.keyword(b1 -> b1.ignoreAbove(256));
                 }
             }
-            builder.properties(name, pBuilder.build());
+            builder.properties(fieldName, pBuilder.build());
 
             for (PropertyDefinition pd : 
indexDefinition.getDynamicBoostProperties()) {
-                builder.properties(pd.nodeName,
+                builder.properties(ElasticIndexUtils.fieldName(pd.nodeName),
                         b1 -> b1.nested(
                                 b2 -> b2.properties(DYNAMIC_BOOST_NESTED_VALUE,
                                                 b3 -> b3.text(
@@ -305,7 +309,9 @@ class ElasticIndexHelper {
                     .similarity(DEFAULT_SIMILARITY_METRIC)
                     .build();
 
-                
builder.properties(FieldNames.createSimilarityFieldName(pd.name), b1 -> 
b1.denseVector(denseVectorProperty));
+                builder.properties(FieldNames.createSimilarityFieldName(
+                        ElasticIndexUtils.fieldName(pd.name)),
+                        b1 -> b1.denseVector(denseVectorProperty));
             }
 
             builder.properties(ElasticIndexDefinition.SIMILARITY_TAGS,
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
index 330aecc167..6b9d71eec2 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
@@ -263,7 +263,8 @@ public class ElasticRequestHandler {
                     continue;
                 }
 
-                String similarityPropFieldName = 
FieldNames.createSimilarityFieldName(pd.name);
+                String similarityPropFieldName = 
FieldNames.createSimilarityFieldName(
+                        ElasticIndexUtils.fieldName(pd.name));
                 KnnQuery knnQuery = 
baseKnnQueryBuilder(similarityPropFieldName, bytes, pd).build();
                 return Optional.of(knnQuery);
             }
@@ -648,13 +649,20 @@ public class ElasticRequestHandler {
     }
 
     private Stream<NestedQuery> dynamicScoreQueries(String text) {
-        return 
elasticIndexDefinition.getDynamicBoostProperties().stream().map(pd -> 
NestedQuery.of(n -> n
-                .path(pd.nodeName)
-                .query(q -> q.functionScore(s -> s
+        return elasticIndexDefinition.getDynamicBoostProperties().stream()
+            .map(pd -> {
+                String field = ElasticIndexUtils.fieldName(pd.nodeName);
+                return NestedQuery.of(n -> n
+                    .path(field)
+                    .query(q -> q.functionScore(s -> s
                         .boost(DYNAMIC_BOOST_WEIGHT)
-                        .query(fq -> fq.match(m -> m.field(pd.nodeName + 
".value").query(FieldValue.of(text))))
-                        .functions(f -> f.fieldValueFactor(fv -> 
fv.field(pd.nodeName + ".boost")))))
-                .scoreMode(ChildScoreMode.Avg))
+                        .query(fq -> fq.match(m -> m.field(
+                                field + ".value").
+                                query(FieldValue.of(text))))
+                        .functions(f -> f.fieldValueFactor(fv -> fv.field(
+                                field + ".boost")))))
+                    .scoreMode(ChildScoreMode.Avg));
+            }
         );
     }
 
@@ -889,8 +897,8 @@ public class ElasticRequestHandler {
                 .type(TextQueryType.CrossFields)
                 .tieBreaker(0.5d);
         if (FieldNames.FULLTEXT.equals(fieldName)) {
-            for(PropertyDefinition pd: 
pr.indexingRule.getNodeScopeAnalyzedProps()) {
-                qsqBuilder.fields(pd.name + "^" + pd.boost);
+            for (PropertyDefinition pd : 
pr.indexingRule.getNodeScopeAnalyzedProps()) {
+                qsqBuilder.fields(ElasticIndexUtils.fieldName(pd.name) + "^" + 
pd.boost);
             }
             // dynamic boost is included only for :fulltext field
             if (includeDynamicBoostedValues) {
@@ -951,6 +959,11 @@ public class ElasticRequestHandler {
         if (planResult.isPathTransformed()) {
             propertyName = PathUtils.getName(propertyName);
         }
-        return propertyName;
+        if ("*".equals(propertyName)) {
+            // elasticsearch does support the pseudo-field "*" meaning all 
fields,
+            // but (arguably) what we really want is the field ":fulltext".
+            return FieldNames.FULLTEXT;
+        }
+        return ElasticIndexUtils.fieldName(propertyName);
     }
 }
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
index dec588c62d..79107050aa 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseListener;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -57,7 +58,9 @@ class ElasticSecureFacetAsyncProvider implements 
ElasticFacetProvider, ElasticRe
     ) {
         this.elasticResponseHandler = elasticResponseHandler;
         this.isAccessible = isAccessible;
-        this.facetFields = 
elasticRequestHandler.facetFields().collect(Collectors.toSet());
+        this.facetFields = elasticRequestHandler.facetFields().
+                map(ElasticIndexUtils::fieldName).
+                collect(Collectors.toSet());
     }
 
     @Override
@@ -129,6 +132,7 @@ class ElasticSecureFacetAsyncProvider implements 
ElasticFacetProvider, ElasticRe
             throw new IllegalStateException("Error while waiting for facets", 
e);
         }
         LOG.trace("Reading facets for {} from {}", columnName, facets);
-        return facets != null ? 
facets.get(FulltextIndex.parseFacetField(columnName)) : null;
+        String field = 
ElasticIndexUtils.fieldName(FulltextIndex.parseFacetField(columnName));
+        return facets != null ? facets.get(field) : null;
     }
 }
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
index f8adf3fab2..ae7aa02618 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
@@ -23,6 +23,7 @@ import 
org.apache.jackrabbit.oak.plugins.index.elastic.ElasticConnection;
 import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
 import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
 import org.slf4j.Logger;
@@ -74,7 +75,9 @@ public class ElasticStatisticalFacetAsyncProvider implements 
ElasticFacetProvide
 
         this.elasticResponseHandler = elasticResponseHandler;
         this.isAccessible = isAccessible;
-        this.facetFields = 
elasticRequestHandler.facetFields().collect(Collectors.toSet());
+        this.facetFields = elasticRequestHandler.facetFields().
+                map(ElasticIndexUtils::fieldName).
+                collect(Collectors.toSet());
 
         SearchRequest searchRequest = SearchRequest.of(srb -> 
srb.index(indexDefinition.getIndexAlias())
                 .trackTotalHits(thb -> thb.enabled(true))
@@ -128,7 +131,8 @@ public class ElasticStatisticalFacetAsyncProvider 
implements ElasticFacetProvide
             throw new IllegalStateException("Error while waiting for facets", 
e);
         }
         LOG.trace("Reading facets for {} from {}", columnName, facets);
-        return facets != null ? 
facets.get(FulltextIndex.parseFacetField(columnName)) : null;
+        String field = 
ElasticIndexUtils.fieldName(FulltextIndex.parseFacetField(columnName));
+        return facets != null ? facets.get(field) : null;
     }
 
     private void processHit(Hit<ObjectNode> searchHit) {
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticIndexUtils.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticIndexUtils.java
index 5332b1c245..b34a908f5e 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticIndexUtils.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticIndexUtils.java
@@ -31,6 +31,69 @@ public class ElasticIndexUtils {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(ElasticIndexUtils.class);
 
+    /**
+     * Convert a JCR property name to a Elasticsearch field name.
+     * Notice that "|" is not allowed in JCR names.
+     *
+     * @param propertyName the property name
+     * @return the field name
+     */
+    public static String fieldName(String propertyName) {
+        if(propertyName.startsWith(":")) {
+            // there are some hardcoded field names
+            return propertyName;
+        }
+        String fieldName = propertyName;
+        boolean blank = fieldName.isBlank();
+        boolean escape = false;
+        if (blank) {
+            // empty field name or field names that only consist of spaces
+            escape = true;
+        } else {
+            // 99.99% property names are OK,
+            // so we loop over the characters first
+            for (int i = 0; i < fieldName.length() && !escape ; i++) {
+                switch (fieldName.charAt(i)) {
+                case '|':
+                case '.':
+                case '^':
+                case '_':
+                    escape = true;
+                }
+            }
+        }
+        if (escape) {
+            StringBuilder buff = new StringBuilder(fieldName.length());
+            if (fieldName.startsWith("_") || blank) {
+                // internal field start with a _
+                // we also support empty or just spaces
+                buff.append('|');
+            }
+            for (int i = 0; i < fieldName.length(); i++) {
+                char c = fieldName.charAt(i);
+                // For performance, the logic for the currently supported
+                // characters is hardcoded.
+                // In case more characters need to be escaped,
+                // buff.append('|').append(Integer.toHexString(c)).append('|');
+                switch (c) {
+                case '|':
+                    buff.append("||");
+                    break;
+                case '.':
+                    buff.append("|2e|");
+                    break;
+                case '^':
+                    buff.append("|5e|");
+                    break;
+                default:
+                    buff.append(c);
+                }
+            }
+            fieldName = buff.toString();
+        }
+        return fieldName;
+    }
+
     /**
      * Transforms a path into an _id compatible with Elasticsearch 
specification. The path cannot be larger than 512
      * bytes. For performance reasons paths that are already compatible are 
returned untouched. Otherwise, SHA-256
@@ -58,7 +121,7 @@ public class ElasticIndexUtils {
      * @return list of floats
      */
     public static List<Float> toFloats(byte[] array) {
-        int blockSize = Float.SIZE / Byte.SIZE;
+        int blockSize = Float.BYTES;
         ByteBuffer wrap = ByteBuffer.wrap(array);
         if (array.length % blockSize != 0) {
             LOG.warn("Unexpected byte array length {}", array.length);
@@ -78,10 +141,9 @@ public class ElasticIndexUtils {
      * @return byte array
      */
     public static byte[] toByteArray(List<Float> values) {
-        int blockSize = Float.SIZE / Byte.SIZE;
-        byte[] bytes = new byte[values.size() * blockSize];
+        byte[] bytes = new byte[values.size() * Float.BYTES];
         ByteBuffer wrap = ByteBuffer.wrap(bytes);
-        for (int i = 0, j = 0; i < values.size(); i++, j += blockSize) {
+        for (int i = 0; i < values.size(); i++) {
             wrap.putFloat(values.get(i));
         }
         return bytes;
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
index 499404aded..690e3efdf8 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
@@ -61,7 +61,7 @@ public class ElasticConnectionRule extends ExternalResource {
     public ElasticConnectionRule(String elasticConnectionString) {
         this(elasticConnectionString,
                 "elastic_test_" +
-                        RandomStringUtils.random(5, true, false).toLowerCase() 
+
+                        RandomStringUtils.insecure().next(5, true, 
false).toLowerCase() +
                         System.currentTimeMillis()
         );
     }
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticContentTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticContentTest.java
index aaa1569093..775339aa24 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticContentTest.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticContentTest.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.plugins.index.elastic;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import 
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
 import org.apache.jackrabbit.oak.stats.StatisticsProvider;
 import org.junit.Ignore;
@@ -285,12 +286,12 @@ public class ElasticContentTest extends 
ElasticAbstractQueryTest {
 
         assertEventually(() -> {
             ObjectNode indexed1 = getDocument(index, "/content/indexed1");
-            assertThat(indexed1.get("a").asText(), equalTo("foo"));
+            
assertThat(indexed1.get(ElasticIndexUtils.fieldName("a")).asText(), 
equalTo("foo"));
 
             ObjectNode indexed2 = getDocument(index, "/content/indexed2");
-            assertThat(indexed2.get("a").size(), equalTo(2));
-            assertThat(indexed2.get("a").get(0).asText(), equalTo("foo"));
-            assertThat(indexed2.get("a").get(1).asText(), equalTo("bar"));
+            assertThat(indexed2.get(ElasticIndexUtils.fieldName("a")).size(), 
equalTo(2));
+            
assertThat(indexed2.get(ElasticIndexUtils.fieldName("a")).get(0).asText(), 
equalTo("foo"));
+            
assertThat(indexed2.get(ElasticIndexUtils.fieldName("a")).get(1).asText(), 
equalTo("bar"));
         });
     }
 
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexAggregationTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexAggregationTest.java
index 174c62c017..204c14f488 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexAggregationTest.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexAggregationTest.java
@@ -16,12 +16,26 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.elastic;
 
+import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT;
+import static org.apache.jackrabbit.JcrConstants.JCR_DATA;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.NT_FILE;
+import static org.apache.jackrabbit.JcrConstants.NT_FOLDER;
+import static 
org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState.binaryProperty;
+
+import java.util.Calendar;
+import java.util.List;
+
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.index.IndexAggregationCommonTest;
 import org.junit.ClassRule;
 import org.junit.Ignore;
 import org.junit.Test;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 public class ElasticIndexAggregationTest extends IndexAggregationCommonTest {
 
@@ -44,4 +58,72 @@ public class ElasticIndexAggregationTest extends 
IndexAggregationCommonTest {
     public void oak3371AggregateV1() throws CommitFailedException {
         super.oak3371AggregateV1();
     }
+
+    @Test
+    public void testChildNodeWithOrCompositePlan() throws Exception {
+        Tree content = root.getTree("/").addChild("content");
+        Tree folder = content.addChild("myFolder");
+        folder.setProperty(JCR_PRIMARYTYPE, NT_FOLDER, Type.NAME);
+        Tree file = folder.addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        file.setProperty("jcr:title", "title");
+        file.setProperty("jcr:description", "description");
+
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+
+        root.commit();
+
+        assertEventually(() -> {
+            String matchContentAll = "//element(*, nt:folder)[(jcr:contains(., 
'dog'))]";
+            assertThat(explainXPath(matchContentAll), containsString(
+                    "\"fields\":[\":fulltext\"],\"query\":\"dog\""));
+            assertQuery(matchContentAll, "xpath", 
List.of("/content/myFolder"));
+
+            String matchContentSimple = "//element(*, 
nt:folder)[(jcr:contains(myFile, 'dog'))]";
+            assertThat(explainXPath(matchContentSimple), containsString(
+                    "\"fields\":[\":fulltext\"],\"query\":\"dog\""));
+            assertQuery(matchContentSimple, "xpath", 
List.of("/content/myFolder"));
+
+            String matchContent = " //element(*, 
nt:folder)[(jcr:contains(myFile, 'dog') or jcr:contains(myFile/@jcr:title, 
'invalid') or jcr:contains(myFile/@jcr:description, 'invalid'))]";
+            assertThat(explainXPath(matchContent), containsString(
+                    "\"fields\":[\":fulltext\"],\"query\":\"dog\""));
+            assertQuery(matchContent, "xpath", List.of("/content/myFolder"));
+
+            String matchTitle = " //element(*, 
nt:folder)[(jcr:contains(myFile, 'invalid') or jcr:contains(myFile/@jcr:title, 
'title') or jcr:contains(myFile/@jcr:description, 'invalid'))]";
+            assertThat(explainXPath(matchTitle), containsString(
+                    "\"fields\":[\":fulltext\"],\"query\":\"invalid\""));
+            assertQuery(matchTitle, "xpath", List.of("/content/myFolder"));
+
+            String matchDesc = " //element(*, nt:folder)[(jcr:contains(myFile, 
'invalid') or jcr:contains(myFile/@jcr:title, 'invalid') or 
jcr:contains(myFile/@jcr:description, 'description'))]";
+            assertThat(explainXPath(matchDesc), containsString(
+                    "\"fields\":[\":fulltext\"],\"query\":\"invalid\""));
+            assertQuery(matchDesc, "xpath", List.of("/content/myFolder"));
+
+            String matchNone = " //element(*, nt:folder)[(jcr:contains(myFile, 
'invalid') or jcr:contains(myFile/@jcr:title, 'invalid') or 
jcr:contains(myFile/@jcr:description, 'invalid'))]";
+            assertThat(explainXPath(matchNone), containsString(
+                    "\"fields\":[\":fulltext\"],\"query\":\"invalid\""));
+            assertQuery(matchNone, "xpath", List.of());
+
+            String matchOnlyTitleOr = " //element(*, 
nt:folder)[(jcr:contains(myFile/@jcr:title, 'title') or 
jcr:contains(myFile/@jcr:title, 'unknown') )]";
+            assertThat(explainXPath(matchOnlyTitleOr), containsString(
+                    "\"fields\":[\"jcr:title\"],\"query\":\"title\""));
+            assertQuery(matchOnlyTitleOr, "xpath", 
List.of("/content/myFolder"));
+        });
+    }
+
+    protected String explainXPath(String query) {
+        return explain(query, XPATH);
+    }
+
+    protected String explain(String query, String language) {
+        String explain = "explain " + query;
+        return executeQuery(explain, language).get(0);
+    }
+
 }
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexSuggestionCommonTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexSuggestionCommonTest.java
index 32e97323eb..040c2c0beb 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexSuggestionCommonTest.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexSuggestionCommonTest.java
@@ -63,7 +63,7 @@ public class ElasticIndexSuggestionCommonTest extends 
IndexSuggestionCommonTest
         String expected = 
"{\"_source\":{\"includes\":[\":path\"]},\"query\":{\"bool\":{\"must\":[{\"nested\":{\"inner_hits\":"
 +
                 
"{\"size\":100},\"path\":\":suggest\",\"query\":{\"match_bool_prefix\":{\":suggest.value\":{\"operator\":\"and\",\"query\":\"boo\"}}},\"score_mode\":\"max\"}}]}},\"size\":100}";
 
-        Query q = qm.createQuery(sql, Query.SQL);
+        Query q = qm.createQuery(sql, Query.JCR_SQL2);
         Row row = q.execute().getRows().nextRow();
         MatcherAssert.assertThat(row.getValue("plan").getString(), 
CoreMatchers.containsString(expected));
     }
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticPropertyIndexTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticPropertyIndexTest.java
index 0e930f5c47..caa597ad98 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticPropertyIndexTest.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticPropertyIndexTest.java
@@ -22,7 +22,10 @@ import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexDefinitionBuilder;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
 import 
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
+import 
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder.PropertyRule;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -177,16 +180,18 @@ public class ElasticPropertyIndexTest extends 
ElasticAbstractQueryTest {
 
     // OAK-11530
     @Test
-    public void propertyWithDot() throws Exception {
+    public void propertyWithDotPrefix() throws Exception {
         IndexDefinitionBuilder builder = createIndex();
         builder.includedPaths("/test")
                 .indexRule("nt:base")
-                .property("test", "./test");
+                .property("foo", "foo").propertyIndex()
+                .property("test", "./test").propertyIndex();
         setIndex("test1", builder);
         root.commit();
 
         //add content
-        root.getTree("/").addChild("test").setProperty("test", "1");
+        root.getTree("/").addChild("test")
+            .setProperty("test", "1");
         root.commit();
 
         String query = "select [jcr:path] from [nt:base] " +
@@ -196,6 +201,59 @@ public class ElasticPropertyIndexTest extends 
ElasticAbstractQueryTest {
             String explanation = explain(query);
             assertThat(explanation, containsString("no-index"));
         });
+
+        String queryFoo = "select [jcr:path] from [nt:base] " +
+                "where foo = '1'";
+        assertEventually(() -> {
+            String explanation = explain(queryFoo);
+            assertThat(explanation, containsString("/oak:index/test1"));
+            assertThat(explanation, 
containsString("{\"term\":{\"foo\":{\"value\":\"1\""));
+            assertQuery(query, List.of());
+        });
+    }
+
+    @Test
+    public void propertyWithDot() throws Exception {
+        IndexDefinitionBuilder builder = createIndex();
+        builder.includedPaths("/test")
+                .indexRule("nt:base")
+                .property("firstName", "first.name").propertyIndex()
+                .property("lowerFirstName", "first.name");
+        PropertyRule lowerFirstName = 
builder.indexRule("nt:base").property("lowerFirstName");
+        lowerFirstName.getBuilderTree().setProperty(
+                FulltextIndexConstants.PROP_FUNCTION, "lower([first.name])");
+        setIndex("test1", builder);
+        root.commit();
+
+        //add content
+        root.getTree("/").addChild("test").setProperty("first.name", 
"Antonio");
+        root.commit();
+
+        String query = "select [jcr:path] from [nt:base] " +
+                "where [first.name] = 'Antonio'";
+
+        assertEventually(() -> {
+            String explanation = explain(query);
+            assertThat(explanation, containsString("/oak:index/test1"));
+            assertThat(explanation, containsString(
+                    "{\"term\":{\"" +
+                            ElasticIndexUtils.fieldName("first.name") +
+                            "\":{\"value\":\"Antonio\""));
+            assertQuery(query, List.of("/test"));
+        });
+
+        String lowerQuery = "select [jcr:path] from [nt:base] " +
+                "where lower([first.name]) = 'antonio'";
+
+        assertEventually(() -> {
+            String explanation = explain(lowerQuery);
+            assertThat(explanation, containsString("/oak:index/test1"));
+            assertThat(explanation, containsString(
+                    "{\"term\":{\"" +
+                            
ElasticIndexUtils.fieldName("function*lower*@first.name") +
+                            "\":{\"value\":\"antonio\""));
+            assertQuery(lowerQuery, List.of("/test"));
+        });
     }
 
     @Test
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticRegexPropertyIndexTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticRegexPropertyIndexTest.java
index 1804a97735..6f4b33ee06 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticRegexPropertyIndexTest.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticRegexPropertyIndexTest.java
@@ -25,6 +25,7 @@ import java.util.List;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
 import 
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
 import 
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder.PropertyRule;
@@ -65,7 +66,10 @@ public class ElasticRegexPropertyIndexTest extends 
ElasticAbstractQueryTest {
         assertEventually(() -> {
             String explain = explain(propaQuery);
             assertThat(explain, containsString("elasticsearch:test1"));
-            assertThat(explain, 
containsString("[{\"term\":{\"flat:allProperties.propa\":{\"value\":\"foo\"}}}]"));
+            assertThat(explain, containsString("[{\"term\":{\"flat:" +
+                    ElasticIndexUtils.fieldName("allProperties") + "." +
+                    ElasticIndexUtils.fieldName("propa") +
+                    "\":{\"value\":\"foo\"}}}]"));
             assertQuery(propaQuery, List.of("/test/a", "/test/b"));
         });
 
@@ -74,8 +78,14 @@ public class ElasticRegexPropertyIndexTest extends 
ElasticAbstractQueryTest {
         assertEventually(() -> {
             String explain = explain(propaOrderQuery);
             assertThat(explain, containsString("elasticsearch:test1"));
-            assertThat(explain, 
containsString("\"query\":{\"bool\":{\"filter\":[{\"prefix\":{\"flat:allProperties.propd\":{\"value\":\"foo\"}}}]}}"));
-            assertThat(explain, 
containsString("\"sort\":[{\"flat:allProperties.propd\":{\"order\":\"asc\"}},{\":path\":{\"order\":\"asc\"}}]"));
+            assertThat(explain, 
containsString("\"query\":{\"bool\":{\"filter\":[{\"prefix\":{\"flat:" +
+                    ElasticIndexUtils.fieldName("allProperties") + "." +
+                    ElasticIndexUtils.fieldName("propd") +
+                    "\":{\"value\":\"foo\"}}}]}}"));
+            assertThat(explain, containsString("\"sort\":[{\"flat:" +
+                    ElasticIndexUtils.fieldName("allProperties") + "." +
+                    ElasticIndexUtils.fieldName("propd") +
+                    
"\":{\"order\":\"asc\"}},{\":path\":{\"order\":\"asc\"}}]"));
             assertThat(explain, containsString("sortOrder: [{ propertyName : 
propd, propertyType : UNDEFINED, order : ASCENDING }]"));
             assertQuery(propaOrderQuery, List.of("/test/f", "/test/e"));
         });
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticSimilarQueryTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticSimilarQueryTest.java
index 6195152a2e..575b55d2f0 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticSimilarQueryTest.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticSimilarQueryTest.java
@@ -25,6 +25,7 @@ import org.apache.commons.io.IOUtils;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
 import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
 import 
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
@@ -174,10 +175,11 @@ public class ElasticSimilarQueryTest extends 
ElasticAbstractQueryTest {
     @Test
     public void vectorSimilarityIndexConfiguration() throws Exception {
         final String indexName = "test1";
-        final String fieldName1 = "fv1";
+        String propertyName = "fv1";
+        final String fieldName1 = ElasticIndexUtils.fieldName(propertyName);
         final String similarityFieldName1 = 
FieldNames.createSimilarityFieldName(fieldName1);
-        IndexDefinitionBuilder builder = createIndex(fieldName1);
-        Tree tree = 
builder.indexRule("nt:base").property(fieldName1).useInSimilarity(true).nodeScopeIndex()
+        IndexDefinitionBuilder builder = createIndex(propertyName);
+        Tree tree = 
builder.indexRule("nt:base").property(propertyName).useInSimilarity(true).nodeScopeIndex()
                 .similaritySearchDenseVectorSize(2048).getBuilderTree();
         tree.setProperty(ElasticPropertyDefinition.PROP_SIMILARITY_METRIC, 
"cosine");
 
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMakerLargeStringPropertiesLogTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMakerLargeStringPropertiesLogTest.java
index a5890e8d17..f77619f7bb 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMakerLargeStringPropertiesLogTest.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMakerLargeStringPropertiesLogTest.java
@@ -73,7 +73,7 @@ public class ElasticDocumentMakerLargeStringPropertiesLogTest 
{
         
System.setProperty(FulltextDocumentMaker.WARN_LOG_STRING_SIZE_THRESHOLD_KEY, 
threshold);
     }
 
-    private ElasticDocumentMaker addPropertyAccordingToType(NodeBuilder test, 
Type type, String... str) {
+    private <T> ElasticDocumentMaker addPropertyAccordingToType(NodeBuilder 
test, Type<T> type, String... str) {
         NodeState root = INITIAL_CONTENT;
         ElasticIndexDefinitionBuilder builder = new 
ElasticIndexDefinitionBuilder();
         builder.indexRule("nt:base")
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java
index 4873aa6e9e..9b7372967e 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java
@@ -28,6 +28,7 @@ import 
co.elastic.clients.elasticsearch.indices.IndexSettingsAnalysis;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexDefinitionBuilder;
+import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils;
 import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
 import 
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -71,7 +72,7 @@ public class ElasticIndexHelperTest {
 
         TypeMapping fooPropertyMappings = request.mappings();
         assertThat(fooPropertyMappings, notNullValue());
-        Property fooProperty = fooPropertyMappings.properties().get("foo");
+        Property fooProperty = 
fooPropertyMappings.properties().get(ElasticIndexUtils.fieldName("foo"));
         assertThat(fooProperty, is(notNullValue()));
         assertThat(fooProperty._kind(), is(Property.Kind.Text));
         TextProperty fooTextProperty = fooProperty.text();
@@ -151,7 +152,7 @@ public class ElasticIndexHelperTest {
 
         TypeMapping fooMappings = request.mappings();
         assertThat(fooMappings, notNullValue());
-        Property fooProperty = fooMappings.properties().get("foo");
+        Property fooProperty = 
fooMappings.properties().get(ElasticIndexUtils.fieldName("foo"));
         assertThat(fooProperty, is(notNullValue()));
         TextProperty textProperty = fooProperty.text();
         assertThat(textProperty.analyzer(), is("oak_analyzer"));
@@ -160,7 +161,7 @@ public class ElasticIndexHelperTest {
 
         TypeMapping barMappings = request.mappings();
         assertThat(barMappings, notNullValue());
-        Property barProperty = barMappings.properties().get("bar");
+        Property barProperty = 
barMappings.properties().get(ElasticIndexUtils.fieldName("bar"));
         assertThat(barProperty._kind(), is(Property.Kind.Keyword));
     }
 
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticIndexUtilsTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticIndexUtilsTest.java
new file mode 100644
index 0000000000..afcb4886fc
--- /dev/null
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticIndexUtilsTest.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elastic.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.Test;
+
+public class ElasticIndexUtilsTest {
+
+    @Test
+    public void fieldName() {
+        assertEquals("regular", ElasticIndexUtils.fieldName("regular"));
+        assertEquals(":nodeName", ElasticIndexUtils.fieldName(":nodeName"));
+        assertEquals("first|2e|name", 
ElasticIndexUtils.fieldName("first.name"));
+        assertEquals("weird|5e|", ElasticIndexUtils.fieldName("weird^"));
+        assertEquals("embedded_is_fine", 
ElasticIndexUtils.fieldName("embedded_is_fine"));
+        assertEquals("|_id", ElasticIndexUtils.fieldName("_id"));
+        assertEquals("|", ElasticIndexUtils.fieldName(""));
+        assertEquals("| ", ElasticIndexUtils.fieldName(" "));
+        assertEquals("||", ElasticIndexUtils.fieldName("|"));
+        assertEquals("||test||", ElasticIndexUtils.fieldName("|test|"));
+    }
+
+    @Test
+    public void randomFieldNames() {
+        propertyNameFromFieldName("");
+        Random r = new Random(1);
+        for (int i = 0; i < 1000; i++) {
+            StringBuilder buff = new StringBuilder();
+            int len = 1 + r.nextInt(5);
+            String chars = "|^._ 25ex";
+            for (int j = 0; j < len; j++) {
+                buff.append(chars.charAt(r.nextInt(chars.length())));
+            }
+            String p = buff.toString();
+            String f = ElasticIndexUtils.fieldName(p);
+            String p2 = propertyNameFromFieldName(f);
+            if (!p.equals(p2)) {
+                p2 = propertyNameFromFieldName(f);
+                assertEquals(p, p2);
+            }
+            // just to make sure there are no exceptions (within some limits)
+            propertyNameFromFieldName(p);
+        }
+    }
+    
+    @Test
+    public void idFromPath() {
+        assertEquals("/content", ElasticIndexUtils.idFromPath("/content"));
+        
assertEquals("%40%0Bz%DF%B4%22%29%EF%BF%BD%EF%BF%BD%3Cfh%EF%BF%BD%27%EF%BF%BD%7E%EF%BF%BDM%EF%BF%BD%EF%BF%BD%EF%BF%BD%22I%EF%BF%BD%7C%EF%BF%BDGn%0A+%25",
 
+                
URLEncoder.encode(ElasticIndexUtils.idFromPath("/content".repeat(100)),StandardCharsets.UTF_8));
+    }
+    
+    @Test
+    public void toByteArray() {
+        assertEquals("[1.0, 0.1]",
+                ElasticIndexUtils.toFloats(
+                ElasticIndexUtils.toByteArray(List.of(1.0f, 
0.1f))).toString());
+        assertEquals("[-0.0, 0.0]",
+                ElasticIndexUtils.toFloats(
+                ElasticIndexUtils.toByteArray(List.of(-0.0f, 
0.0f))).toString());
+        assertEquals("[Infinity, -Infinity]",
+                ElasticIndexUtils.toFloats(
+                ElasticIndexUtils.toByteArray(List.of(Float.POSITIVE_INFINITY, 
Float.NEGATIVE_INFINITY))).toString());
+        assertEquals("[NaN, 3.4028235E38]",
+                ElasticIndexUtils.toFloats(
+                ElasticIndexUtils.toByteArray(List.of(Float.NaN, 
Float.MAX_VALUE))).toString());
+    }
+
+    /**
+     * Convert an elasticsearch field name to a JCR property name.
+     * Please note this method is not optimized for performance.
+     *
+     * @param fieldName the field name
+     * @return the property name
+     */
+    public static String propertyNameFromFieldName(String fieldName) {
+        if (fieldName.indexOf('|') < 0) {
+            return fieldName;
+        }
+        if (fieldName.startsWith("|")) {
+            if (fieldName.equals("|")) {
+                return "";
+            } if (fieldName.startsWith("|_") || 
fieldName.substring(1).isBlank()) {
+                fieldName = fieldName.substring(1);
+            }
+        }
+        StringBuilder buff = new StringBuilder(fieldName.length());
+        for (int i = 0; i < fieldName.length(); i++) {
+            char c = fieldName.charAt(i);
+            switch (c) {
+            case '|':
+                String next = fieldName.substring(i + 1);
+                if (next.startsWith("|")) {
+                    buff.append('|');
+                    i++;
+                } else {
+                    int end = next.indexOf('|');
+                    if (end < 0) {
+                        buff.append(next);
+                        break;
+                    }
+                    String code = next.substring(0, end);
+                    try {
+                        buff.append((char) Integer.parseInt(code, 16));
+                    } catch (NumberFormatException e) {
+                        buff.append(code);
+                    }
+                    i += code.length() + 1;
+                }
+                break;
+            default:
+                buff.append(c);
+            }
+        }
+        return buff.toString();
+    }
+
+}
diff --git 
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexPlannerCommonTest.java
 
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexPlannerCommonTest.java
index e8a557e923..881610482f 100644
--- 
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexPlannerCommonTest.java
+++ 
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexPlannerCommonTest.java
@@ -1508,7 +1508,7 @@ public abstract class IndexPlannerCommonTest {
     }
 
     private static String generateRandomIndexName(String prefix) {
-        return prefix + RandomStringUtils.random(5, true, false);
+        return prefix + RandomStringUtils.insecure().next(5, true, false);
     }
 
     /**

Reply via email to