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 b3b1c587af OAK-10549 Improve performance of facet count at scale
(Lucene) (#1215)
b3b1c587af is described below
commit b3b1c587aff6b6e704f4f5d543e7b1cd13665f95
Author: Thomas Mueller <[email protected]>
AuthorDate: Fri Nov 17 14:24:55 2023 +0100
OAK-10549 Improve performance of facet count at scale (Lucene) (#1215)
* OAK-10549 Improve performance of facet count at scale (Lucene)
* OAK-10549 Improve performance of facet count at scale (Lucene)
* OAK-10549 Improve performance of facet count at scale (Lucene)
Co-authored-by: Fabrizio Fortino <[email protected]>
---------
Co-authored-by: Fabrizio Fortino <[email protected]>
---
.../plugins/index/lucene/LucenePropertyIndex.java | 53 ++++
.../oak/plugins/index/lucene/util/FacetHelper.java | 26 +-
.../index/lucene/hybrid/ManyFacetsTest.java | 280 +++++++++++++++++++++
.../plugins/index/lucene/util/FacetHelperTest.java | 14 --
.../index/search/spi/query/FulltextIndex.java | 19 ++
.../index/search/spi/query/FulltextIndexTest.java | 13 +
6 files changed, 386 insertions(+), 19 deletions(-)
diff --git
a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
index 036b112fda..69421c0c4b 100644
---
a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
+++
b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
@@ -209,6 +209,9 @@ public class LucenePropertyIndex extends FulltextIndex {
public final static String CACHE_FACET_RESULTS_NAME =
"oak.lucene.cacheFacetResults";
private final boolean CACHE_FACET_RESULTS =
Boolean.parseBoolean(System.getProperty(CACHE_FACET_RESULTS_NAME,
"true"));
+ public final static String EAGER_FACET_CACHE_FILL_NAME =
"oak.lucene.cacheFacetEagerFill";
+ private final static boolean EAGER_FACET_CACHE_FILL =
+
Boolean.parseBoolean(System.getProperty(EAGER_FACET_CACHE_FILL_NAME, "true"));
private static boolean FLAG_CACHE_FACET_RESULTS_CHANGE = true;
@@ -1644,11 +1647,39 @@ public class LucenePropertyIndex extends FulltextIndex {
return cachedResults.get(cacheKey);
}
LOG.trace("columnName = {} facet Data not present in cache...",
columnName);
+ if (EAGER_FACET_CACHE_FILL) {
+ fillFacetCache(numberOfFacets);
+ if (cachedResults.containsKey(cacheKey)) {
+ LOG.trace("columnName = {} now found", cacheKey);
+ return cachedResults.get(cacheKey);
+ }
+ LOG.warn("Facet data for {} not found: read using query",
cacheKey);
+ }
List<Facet> result = getFacetsUncached(numberOfFacets, columnName);
cachedResults.put(cacheKey, result);
return result;
}
+ private List<Facet> fillFacetCache(int numberOfFacets) throws
IOException {
+ List<Facet> result = null;
+ LuceneIndexNode indexNode = index.acquireIndexNode(plan);
+ try {
+ IndexSearcher searcher = indexNode.getSearcher();
+ Facets facets = FacetHelper.getFacets(searcher, query, plan,
config);
+ if (facets != null) {
+ List<String> allColumnNames =
FacetHelper.getFacetColumnNamesFromPlan(plan);
+ for (String column : allColumnNames) {
+ result = getFacetsUncached(facets, numberOfFacets,
column);
+ String cc = column + "/" + numberOfFacets;
+ cachedResults.put(cc, result);
+ }
+ }
+ } finally {
+ indexNode.release();
+ }
+ return result;
+ }
+
private List<Facet> getFacetsUncached(int numberOfFacets, String
columnName) throws IOException {
LuceneIndexNode indexNode = index.acquireIndexNode(plan);
try {
@@ -1677,6 +1708,28 @@ public class LucenePropertyIndex extends FulltextIndex {
indexNode.release();
}
}
+
+ private List<Facet> getFacetsUncached(Facets facets, int
numberOfFacets, String columnName) throws IOException {
+ String facetFieldName = FulltextIndex.parseFacetField(columnName);
+ try {
+ ImmutableList.Builder<Facet> res = new
ImmutableList.Builder<>();
+ FacetResult topChildren =
facets.getTopChildren(numberOfFacets, facetFieldName);
+ if (topChildren == null) {
+ return null;
+ }
+ for (LabelAndValue lav : topChildren.labelValues) {
+ res.add(new Facet(
+ lav.label, lav.value.intValue()
+ ));
+ }
+ return res.build();
+ } catch (IllegalArgumentException iae) {
+ LOG.debug(iae.getMessage(), iae);
+ LOG.warn("facets for {} not yet indexed: {}", facetFieldName,
iae);
+ return null;
+ }
+ }
+
}
static class LuceneFacetProvider implements FacetProvider {
diff --git
a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
index 13dabce9fb..37eb574d2e 100644
---
a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
+++
b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
@@ -19,14 +19,17 @@
package org.apache.jackrabbit.oak.plugins.index.lucene.util;
import java.io.IOException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
import
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.SecureFacetConfiguration;
-import org.apache.jackrabbit.oak.spi.query.QueryConstants;
+import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
import org.apache.jackrabbit.oak.spi.query.QueryIndex;
+import org.apache.jackrabbit.oak.spi.query.QueryIndex.IndexPlan;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.lucene.facet.FacetResult;
import org.apache.lucene.facet.Facets;
@@ -60,6 +63,23 @@ public class FacetHelper {
return new NodeStateFacetsConfig(definition);
}
+ /**
+ * Get the column names of all the facets from the index plan, if any.
+ *
+ * @param plan the plan
+ * @return a list (possibly empty)
+ */
+ public static List<String> getFacetColumnNamesFromPlan(IndexPlan plan) {
+ @SuppressWarnings("unchecked")
+ List<String> facetFields = (List<String>)
plan.getAttribute(ATTR_FACET_FIELDS);
+ if (facetFields == null) {
+ return Collections.emptyList();
+ }
+ return facetFields.stream().map(
+ FulltextIndex::convertFacetFieldNameToColumnName).
+ collect(Collectors.toList());
+ }
+
public static Facets getFacets(IndexSearcher searcher, Query query,
QueryIndex.IndexPlan plan,
SecureFacetConfiguration
secureFacetConfiguration) throws IOException {
Facets facets = null;
@@ -104,10 +124,6 @@ public class FacetHelper {
return facets;
}
- public static String parseFacetField(String columnName) {
- return columnName.substring(QueryConstants.REP_FACET.length() + 1,
columnName.length() - 1);
- }
-
private static final Facets NULL_FACETS = new Facets() {
@Override
public FacetResult getTopChildren(int topN, String dim, String...
path) {
diff --git
a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/hybrid/ManyFacetsTest.java
b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/hybrid/ManyFacetsTest.java
new file mode 100644
index 0000000000..3a6f5ee2ac
--- /dev/null
+++
b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/hybrid/ManyFacetsTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.lucene.hybrid;
+
+import static
org.apache.jackrabbit.guava.common.util.concurrent.MoreExecutors.newDirectExecutorService;
+import static
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_FACETS;
+import static
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT;
+import static
org.apache.jackrabbit.oak.spi.mount.Mounts.defaultMountInfoProvider;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+import javax.jcr.GuestCredentials;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
+
+import org.apache.jackrabbit.oak.InitialContent;
+import org.apache.jackrabbit.oak.Oak;
+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.commons.PathUtils;
+import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser;
+import org.apache.jackrabbit.oak.commons.json.JsonObject;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.plugins.index.AsyncIndexUpdate;
+import
org.apache.jackrabbit.oak.plugins.index.counter.NodeCounterEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.IndexCopier;
+import org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker;
+import
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LucenePropertyIndex;
+import org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil;
+import
org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.OptionalEditorProvider;
+import
org.apache.jackrabbit.oak.plugins.index.lucene.reader.DefaultIndexReaderFactory;
+import
org.apache.jackrabbit.oak.plugins.index.lucene.reader.LuceneIndexReaderFactory;
+import
org.apache.jackrabbit.oak.plugins.index.lucene.util.LuceneIndexDefinitionBuilder;
+import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider;
+import
org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
+import
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder.PropertyRule;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.query.AbstractQueryTest;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
+import org.apache.jackrabbit.oak.stats.Clock;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.jetbrains.annotations.Nullable;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class ManyFacetsTest extends AbstractQueryTest {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder(new
File("target"));
+
+ private static final int NUM_LABELS = 4;
+ private static final int NUM_LEAF_NODES =
STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT;
+ private static final String FACET_PROP = "facets";
+ private static final long REFRESH_DELTA = TimeUnit.SECONDS.toMillis(1);
+
+ private static final int FACET_COUNT = 200;
+
+ private ExecutorService executorService = Executors.newFixedThreadPool(2);
+ private OptionalEditorProvider optionalEditorProvider = new
OptionalEditorProvider();
+ private NRTIndexFactory nrtIndexFactory;
+ private LuceneIndexProvider luceneIndexProvider;
+ private NodeStore nodeStore;
+ private DocumentQueue queue;
+ private Clock clock = new Clock.Virtual();
+ private Whiteboard wb;
+ private QueryManager qm;
+ private Repository jcrRepo;
+ private Jcr jcr;
+ private Oak oak;
+ // backup original system properties i.e. before test started
+ private final Properties backupProperties = (Properties)
System.getProperties().clone();
+
+ @After
+ public void tearDown() throws IOException {
+ luceneIndexProvider.close();
+ new ExecutorCloser(executorService).close();
+ nrtIndexFactory.close();
+ // restore original system properties i.e. before test started
+ System.setProperties(backupProperties);
+ }
+
+ @Override
+ protected ContentRepository createRepository() {
+ IndexCopier copier;
+ try {
+ copier = new IndexCopier(executorService,
temporaryFolder.getRoot());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ MountInfoProvider mip = defaultMountInfoProvider();
+ nrtIndexFactory = new NRTIndexFactory(copier, clock,
TimeUnit.MILLISECONDS.toSeconds(REFRESH_DELTA), StatisticsProvider.NOOP);
+ nrtIndexFactory.setAssertAllResourcesClosed(true);
+ LuceneIndexReaderFactory indexReaderFactory = new
DefaultIndexReaderFactory(mip, copier);
+ IndexTracker tracker = new IndexTracker(indexReaderFactory,
nrtIndexFactory);
+ luceneIndexProvider = new LuceneIndexProvider(tracker);
+ queue = new DocumentQueue(100, tracker, newDirectExecutorService());
+ LuceneIndexEditorProvider editorProvider = new
LuceneIndexEditorProvider(copier,
+ tracker,
+ null,
+ null,
+ mip);
+ editorProvider.setIndexingQueue(queue);
+ LocalIndexObserver localIndexObserver = new LocalIndexObserver(queue,
StatisticsProvider.NOOP);
+ nodeStore = new MemoryNodeStore();
+ oak = new Oak(nodeStore)
+ .with(new InitialContent())
+ .with(new OpenSecurityProvider())
+ .with((QueryIndexProvider) luceneIndexProvider)
+ .with((Observer) luceneIndexProvider)
+ .with(localIndexObserver)
+ .with(editorProvider)
+ .with(new PropertyIndexEditorProvider())
+ .with(new NodeTypeIndexProvider())
+ .with(optionalEditorProvider)
+ .with(new NodeCounterEditorProvider())
+ //Effectively disable async indexing auto run
+ //such that we can control run timing as per test requirement
+ .withAsyncIndexing("async", TimeUnit.DAYS.toSeconds(1));
+
+ wb = oak.getWhiteboard();
+ ContentRepository repo = oak.createContentRepository();
+ return repo;
+ }
+
+ private void createSmallDataset(int k) throws RepositoryException {
+ Random random = new Random(42);
+ Tree par = createPath("/parent" + k);
+ par.setProperty("foo", "bar");
+ for (int i = 0; i < NUM_LABELS * 2; i++) {
+ Tree subPar = par.addChild("par" + i);
+ for (int j = 0; j < NUM_LEAF_NODES / (2 * NUM_LABELS); j++) {
+ Tree child = subPar.addChild("c" + j);
+ child.setProperty("cons", "val");
+ for (int f = 0; f < FACET_COUNT; f++) {
+ int labelNum = random.nextInt(NUM_LABELS);
+ child.setProperty("foo" + f, "foo" + f + "x" + labelNum);
+ }
+ }
+ }
+ }
+
+ private Tree createPath(String path) {
+ Tree base = root.getTree("/");
+ for (String e : PathUtils.elements(path)) {
+ base = base.addChild(e);
+ }
+ return base;
+ }
+
+ private void runAsyncIndex() {
+ AsyncIndexUpdate async = (AsyncIndexUpdate)
WhiteboardUtils.getService(wb, Runnable.class, new Predicate<Runnable>() {
+ @Override
+ public boolean test(@Nullable Runnable input) {
+ return input instanceof AsyncIndexUpdate;
+ }
+ });
+ assertNotNull(async);
+ async.run();
+ if (async.isFailing()) {
+ fail("AsyncIndexUpdate failed");
+ }
+ root.refresh();
+ }
+
+ @Test
+ public void facet() throws Exception {
+ // Explicitly setting following configs to run
DelayedLuceneFacetProvider and a thread sleep of 50 ms in refresh readers.
Refer: OAK-8898
+ System.setProperty(LucenePropertyIndex.OLD_FACET_PROVIDER_CONFIG_NAME,
"false");
+ // The variable is static final so once set it remains same for all
tests and which will lead to slow execution
+ // of other tests as this add a sleep of specified milliseconds in
refresh reader method in LuceneIndexNodeManager.
+ //
System.setProperty(LuceneIndexNodeManager.OLD_FACET_PROVIDER_TEST_FAILURE_SLEEP_INSTRUMENT_NAME,
"40");
+ Thread.currentThread().setName("main");
+ String idxName = "hybridtest";
+ Tree idx = createIndex(root.getTree("/"), idxName);
+ TestUtil.enableIndexingMode(idx,
FulltextIndexConstants.IndexingMode.NRT);
+ setTraversalEnabled(false);
+ root.commit();
+ jcr = new Jcr(oak);
+ jcrRepo = jcr.createRepository();
+ createSmallDataset(0);
+ clock.waitUntil(clock.getTime() + REFRESH_DELTA + 1);
+ root.commit();
+ runAsyncIndex();
+ createSmallDataset(2);
+ clock.waitUntil(clock.getTime() + REFRESH_DELTA + 1);
+ root.commit();
+ Session anonSession = jcrRepo.login(new GuestCredentials());
+ qm = anonSession.getWorkspace().getQueryManager();
+ String facetList = "";
+ for (int i = 0; i < FACET_COUNT; i++) {
+ if (i > 0) {
+ facetList += ", ";
+ }
+ facetList += "[rep:facet(foo" + i + ")]";
+ }
+ String queryString = "SELECT " + facetList +
+ " FROM [nt:base] WHERE [cons] = 'val'";
+ Query q = qm.createQuery(queryString, SQL2);
+ QueryResult qr = q.execute();
+ try {
+ RowIterator it = qr.getRows();
+ assertTrue(it.hasNext());
+ while (it.hasNext()) {
+ Row r = it.nextRow();
+ for (int i = 0; i < qr.getColumnNames().length; i++) {
+ String columnName = qr.getColumnNames()[i];
+ String v = r.getValue(columnName).getString();
+ JsonObject json = JsonObject.fromJson(v, true);
+ for (int j = 0; j < NUM_LABELS; j++) {
+ String n = json.getProperties().get("foo" + i + "x" +
j);
+ assertNotNull(n);
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ private Tree createIndex(Tree index, String name) throws
RepositoryException {
+ LuceneIndexDefinitionBuilder idxBuilder = new
LuceneIndexDefinitionBuilder();
+ PropertyRule pr = idxBuilder.noAsync()
+ .indexRule("nt:base")
+ .property("cons").propertyIndex();
+ for (int i = 0; i < FACET_COUNT; i++) {
+ pr.property("foo" +
i).propertyIndex().getBuilderTree().setProperty(PROP_FACETS, true);
+ }
+ Tree facetConfig = idxBuilder.getBuilderTree().addChild(FACET_PROP);
+ facetConfig.setProperty("jcr:primaryType", "nt:unstructured",
Type.NAME);
+ facetConfig.setProperty("secure", "statistical");
+ facetConfig.setProperty("topChildren", "100");
+ Tree idxTree = index.getChild("oak:index").addChild(name);
+ idxBuilder.build(idxTree);
+ return idxTree;
+ }
+
+}
diff --git
a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelperTest.java
b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelperTest.java
index ee9a38d6eb..ce4894daec 100644
---
a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelperTest.java
+++
b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelperTest.java
@@ -23,7 +23,6 @@ import org.apache.lucene.facet.FacetsConfig;
import org.junit.Test;
import static org.apache.jackrabbit.oak.InitialContentHelper.INITIAL_CONTENT;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
@@ -39,17 +38,4 @@ public class FacetHelperTest {
assertNotNull(facetsConfig);
}
- @Test
- public void testParseFacetField() throws Exception {
- String field = FacetHelper.parseFacetField("rep:facet(text)");
- assertNotNull(field);
- assertEquals("text", field);
- field = FacetHelper.parseFacetField("rep:facet(jcr:title)");
- assertNotNull(field);
- assertEquals("jcr:title", field);
- field = FacetHelper.parseFacetField("rep:facet(jcr:primaryType)");
- assertNotNull(field);
- assertEquals("jcr:primaryType", field);
-
- }
}
\ No newline at end of file
diff --git
a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java
b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java
index 38dd82d953..4b469f56b5 100644
---
a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java
+++
b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java
@@ -615,7 +615,26 @@ public abstract class FulltextIndex implements
AdvancedQueryIndex, QueryIndex, N
}
}
+ /**
+ * Get the facet name from a column name.
+ *
+ * This method silently assumes(!) that the column name starts with
"rep:facet("
+ * and ends with ")".
+ *
+ * @param columnName the column name, e.g. "rep:facet(abc)"
+ * @return the facet name, e.g. "abc"
+ */
public static String parseFacetField(String columnName) {
return columnName.substring(QueryConstants.REP_FACET.length() + 1,
columnName.length() - 1);
}
+
+ /**
+ * Convert the facet name to a column name.
+ *
+ * @param facetFieldName the facet field name, e.g. "abc"
+ * @return the column name, e.g. "rep:facet(abc)"
+ */
+ public static String convertFacetFieldNameToColumnName(String
facetFieldName) {
+ return QueryConstants.REP_FACET + "(" + facetFieldName + ")";
+ }
}
diff --git
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexTest.java
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexTest.java
index 316081b597..17dc3995a5 100644
---
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexTest.java
+++
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexTest.java
@@ -59,6 +59,19 @@ public class FulltextIndexTest {
assertEquals("jcr:primaryType", field);
}
+ @Test
+ public void testConvertParseFacetField() {
+ assertEquals("rep:facet(text)",
+ FulltextIndex.convertFacetFieldNameToColumnName(
+ "text"));
+ assertEquals("rep:facet(jcr:title)",
+ FulltextIndex.convertFacetFieldNameToColumnName(
+ "jcr:title"));
+ assertEquals("rep:facet(jcr:primaryType)",
+ FulltextIndex.convertFacetFieldNameToColumnName(
+ "jcr:primaryType"));
+ }
+
/**
* Test that we can read the rows first, and then read the data from the
rows.
*/