Author: catholicon
Date: Wed Nov 1 08:49:52 2017
New Revision: 1813955
URL: http://svn.apache.org/viewvc?rev=1813955&view=rev
Log:
OAK-6735: Lucene Index: improved cost estimation by using document count per
field
Added:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatistics.java
(with props)
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatisticsTest.java
(with props)
Modified:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNode.java
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNodeManager.java
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiplexingLucenePropertyIndexTest.java
Modified:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNode.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNode.java?rev=1813955&r1=1813954&r2=1813955&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNode.java
(original)
+++
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNode.java
Wed Nov 1 08:49:52 2017
@@ -36,6 +36,8 @@ public interface IndexNode {
IndexSearcher getSearcher();
+ IndexStatistics getIndexStatistics();
+
IndexDefinition getDefinition();
List<LuceneIndexReader> getPrimaryReaders();
Modified:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNodeManager.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNodeManager.java?rev=1813955&r1=1813954&r2=1813955&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNodeManager.java
(original)
+++
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexNodeManager.java
Wed Nov 1 08:49:52 2017
@@ -275,10 +275,16 @@ public class IndexNodeManager {
final IndexSearcher searcher;
final List<LuceneIndexReader> nrtReaders;
final int searcherId = SEARCHER_ID_COUNTER.incrementAndGet();
+ final IndexStatistics indexStatistics;
public SearcherHolder(IndexSearcher searcher, List<LuceneIndexReader>
nrtReaders) {
this.searcher = searcher;
this.nrtReaders = nrtReaders;
+ this.indexStatistics = new
IndexStatistics(searcher.getIndexReader());
+ }
+
+ public IndexStatistics getIndexStatistics() {
+ return indexStatistics;
}
}
@@ -308,6 +314,11 @@ public class IndexNodeManager {
}
@Override
+ public IndexStatistics getIndexStatistics() {
+ return holder.getIndexStatistics();
+ }
+
+ @Override
public IndexDefinition getDefinition() {
return definition;
}
Modified:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java?rev=1813955&r1=1813954&r2=1813955&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
(original)
+++
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
Wed Nov 1 08:49:52 2017
@@ -48,7 +48,6 @@ import org.apache.jackrabbit.oak.spi.que
import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextVisitor;
import org.apache.jackrabbit.oak.spi.query.Filter;
import org.apache.jackrabbit.oak.spi.query.QueryConstants;
-import org.apache.lucene.index.IndexReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -255,26 +254,14 @@ class IndexPlanner {
boolean canSort = canSortByProperty(sortOrder);
if (!indexedProps.isEmpty() || canSort || ft != null
|| evalPathRestrictions || evalNodeTypeRestrictions ||
canEvalNodeNameRestriction) {
- //TODO Need a way to have better cost estimate to indicate that
- //this index can evaluate more propertyRestrictions natively (if
more props are indexed)
- //For now we reduce cost per entry
-
- //Use propDefns instead of indexedProps as it determines true
count of property restrictions
- //which are evaluated by this index
- int costPerEntryFactor = result.propDefns.size();
+ int costPerEntryFactor = 1;
costPerEntryFactor += sortOrder.size();
- //this index can evaluate more propertyRestrictions natively (if
more props are indexed)
- //For now we reduce cost per entry
IndexPlan.Builder plan = defaultPlan();
if (!sortOrder.isEmpty()) {
plan.setSortOrder(sortOrder);
}
- if (costPerEntryFactor == 0){
- costPerEntryFactor = 1;
- }
-
if (facetFields.size() > 0) {
plan.setAttribute(FacetHelper.ATTR_FACET_FIELDS, facetFields);
}
@@ -291,6 +278,11 @@ class IndexPlanner {
result.enableNodeNameRestriction();
}
+ // Set a index based guess here. Unique would set its own value
below
+ if (useActualEntryCount && !definition.isEntryCountDefined()) {
+
plan.setEstimatedEntryCount(getMaxPossibleNumDocs(result.propDefns));
+ }
+
if (sortOrder.isEmpty() && ft == null) {
boolean uniqueIndexFound = planForSyncIndexes(indexingRule);
if (uniqueIndexFound) {
@@ -717,7 +709,7 @@ class IndexPlanner {
}
private long estimatedEntryCount() {
- int numOfDocs = getReader().numDocs();
+ int numOfDocs = getNumDocs();
if (useActualEntryCount) {
return definition.isEntryCountDefined() ?
definition.getEntryCount() : numOfDocs;
} else {
@@ -742,8 +734,37 @@ class IndexPlanner {
return PathUtils.denotesRoot(parentPath) ? "" : parentPath;
}
- private IndexReader getReader() {
- return indexNode.getSearcher().getIndexReader();
+ private int getNumDocs() {
+ return indexNode.getIndexStatistics().numDocs();
+ }
+
+ private int getMaxPossibleNumDocs(Map<String, PropertyDefinition>
propDefns) {
+ IndexStatistics indexStatistics = indexNode.getIndexStatistics();
+ int minNumDocs = indexStatistics.numDocs();
+ for (Map.Entry<String, PropertyDefinition> propDef :
propDefns.entrySet()) {
+ int docCntForField =
indexStatistics.getDocCountFor(propDef.getKey());
+ if (docCntForField == -1) {
+ continue;
+ }
+
+ int weight = propDef.getValue().weight;
+
+ if (weight > 1) {
+ // use it to scale down the doc count - in broad strokes, we
can think of weight
+ // as number of terms for the field with all terms getting
equal share of
+ // the documents in this field
+ double scaledDocCnt = Math.ceil((double) docCntForField /
weight);
+ if (minNumDocs < scaledDocCnt) {
+ continue;
+ }
+ // since, we've already taken care that scaled cost is lower
than minCost,
+ // we can safely cast without risking overflow
+ minNumDocs = (int)scaledDocCnt;
+ } else if (docCntForField < minNumDocs) {
+ minNumDocs = docCntForField;
+ }
+ }
+ return minNumDocs;
}
private List<OrderEntry> createSortOrder(IndexingRule rule) {
Added:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatistics.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatistics.java?rev=1813955&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatistics.java
(added)
+++
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatistics.java
Wed Nov 1 08:49:52 2017
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+import com.google.common.collect.Maps;
+import org.apache.lucene.index.Fields;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.MultiFields;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * This class would populate some statistics from a reader. We want to be
careful here such that
+ * we only collect statistics which don't incur reads from the index i.e. we
would only collect
+ * stats that lucene would already have read into memory when the reader was
opened.
+ */
+public class IndexStatistics {
+ static final Logger LOG = LoggerFactory.getLogger(IndexStatistics.class);
+ private final int numDocs;
+ private final Map<String, Integer> numDocsForField;
+
+ /**
+ * @param reader {@link IndexReader} for which statistics need to be
collected.
+ */
+ public IndexStatistics(IndexReader reader) {
+ numDocs = reader.numDocs();
+
+ Map<String, Integer> numDocsForField = Maps.newHashMap();
+
+ Fields fields = null;
+ try {
+ fields = MultiFields.getFields(reader);
+ } catch (IOException e) {
+ LOG.warn("Couldn't open fields for reader ({}). Won't extract doc
count per field", reader);
+ numDocsForField = null;
+ }
+
+ if (fields != null) {
+ for(String f : fields) {
+ if (isPropertyField(f)) {
+ int docCntForField = numDocs;
+ try {
+ docCntForField = reader.getDocCount(f);
+ } catch (IOException e) {
+ LOG.warn("Couldn't read doc count for field {} via
reader ({}). Would use numDocs for this field");
+ }
+ numDocsForField.put(f, docCntForField);
+ }
+ }
+ }
+
+ if (numDocsForField != null) {
+ this.numDocsForField =
Collections.unmodifiableMap(numDocsForField);
+ } else {
+ this.numDocsForField = null;
+ }
+ }
+
+ /**
+ * @return number of documents in the index
+ */
+ public int numDocs() {
+ return numDocs;
+ }
+
+ /**
+ * @param field Index field for which number of indexed documents are to
be return
+ * @return number of indexed documents (without subtracting potentially
deleted ones)
+ * for the given {@code field}.
+ */
+ public int getDocCountFor(String field) {
+ int docCntForField = isPropertyField(field) ? 0 : -1;
+ if (numDocsForField.containsKey(field)) {
+ docCntForField = numDocsForField.get(field);
+ }
+
+ return docCntForField;
+ }
+
+ private boolean isPropertyField(String field) {
+ return !field.startsWith(FieldNames.ANALYZED_FIELD_PREFIX)
+ && !field.startsWith(FieldNames.FULLTEXT_RELATIVE_NODE)
+ && !field.startsWith(":")
+ && !field.endsWith("_facet");
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatistics.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java?rev=1813955&r1=1813954&r2=1813955&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
(original)
+++
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
Wed Nov 1 08:49:52 2017
@@ -211,7 +211,7 @@ public class LuceneIndex implements Adva
if (node != null){
IndexDefinition defn = node.getDefinition();
return Collections.singletonList(planBuilder(filter)
-
.setEstimatedEntryCount(defn.getFulltextEntryCount(node.getSearcher().getIndexReader().numDocs()))
+
.setEstimatedEntryCount(defn.getFulltextEntryCount(node.getIndexStatistics().numDocs()))
.setCostPerExecution(defn.getCostPerExecution())
.setCostPerEntry(defn.getCostPerEntry())
.setAttribute(ATTR_INDEX_PATH, indexPath)
Modified:
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java?rev=1813955&r1=1813954&r2=1813955&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
(original)
+++
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
Wed Nov 1 08:49:52 2017
@@ -30,9 +30,12 @@ import static org.apache.jackrabbit.oak.
import static
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.INDEX_DATA_CHILD_NAME;
import static
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.INDEX_RULES;
import static
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.ORDERED_PROP_NAMES;
+import static
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.PROP_FUNCTION;
import static
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.VERSION;
import static org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.NT_TEST;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.child;
import static
org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.registerTestNodeType;
+import static
org.apache.jackrabbit.oak.plugins.index.lucene.util.FunctionIndexProcessor.*;
import static
org.apache.jackrabbit.oak.plugins.index.lucene.util.LuceneIndexHelper.newLuceneIndexDefinition;
import static
org.apache.jackrabbit.oak.plugins.index.lucene.util.LuceneIndexHelper.newLucenePropertyIndexDefinition;
import static
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
@@ -54,6 +57,8 @@ import java.util.List;
import javax.annotation.Nonnull;
+import com.google.common.collect.Lists;
+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.plugins.index.IndexConstants;
@@ -61,6 +66,7 @@ import org.apache.jackrabbit.oak.plugins
import
org.apache.jackrabbit.oak.plugins.index.lucene.reader.DefaultIndexReader;
import org.apache.jackrabbit.oak.plugins.index.lucene.reader.LuceneIndexReader;
import
org.apache.jackrabbit.oak.plugins.index.lucene.reader.LuceneIndexReaderFactory;
+import
org.apache.jackrabbit.oak.plugins.index.lucene.util.FunctionIndexProcessor;
import
org.apache.jackrabbit.oak.plugins.index.lucene.util.IndexDefinitionBuilder;
import org.apache.jackrabbit.oak.plugins.memory.PropertyValues;
import org.apache.jackrabbit.oak.query.NodeStateNodeTypeInfoProvider;
@@ -519,20 +525,20 @@ public class IndexPlannerTest {
@Test
public void indexedButZeroWeightProps() throws Exception{
IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
- defnb.indexRule("nt:base").property("foo").propertyIndex().weight(0);
- defnb.indexRule("nt:base").property("bar").propertyIndex();
+ defnb.indexRule("nt:base").property("foo").propertyIndex();
+ defnb.indexRule("nt:base").property("bar").propertyIndex().weight(0);
IndexDefinition defn = new IndexDefinition(root, defnb.build(),
"/foo");
IndexNode node = createIndexNode(defn);
FilterImpl filter = createFilter("nt:base");
- filter.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("a"));
+ filter.restrictProperty("bar", Operator.EQUAL,
PropertyValues.newString("a"));
IndexPlanner planner = new IndexPlanner(node, "/foo", filter,
Collections.<OrderEntry>emptyList());
//Even though foo is indexed it would not be considered for a query
involving just foo
assertNull(planner.getPlan());
filter = createFilter("nt:base");
- filter.restrictProperty("bar", Operator.EQUAL,
PropertyValues.newString("a"));
+ filter.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("a"));
planner = new IndexPlanner(node, "/foo", filter,
Collections.<OrderEntry>emptyList());
QueryIndex.IndexPlan plan1 = planner.getPlan();
assertNotNull(plan1);
@@ -544,9 +550,10 @@ public class IndexPlannerTest {
QueryIndex.IndexPlan plan2 = planner.getPlan();
assertNotNull(plan2);
- //For plan2 as 2 props are indexed its costPerEntry should be less
than plan1 which
- //indexes only one prop
- assertThat(plan2.getCostPerEntry(), lessThan(plan1.getCostPerEntry()));
+
+ // Since, the index has no entries for "bar", estimated entry count
for plan2 would be 0
+ assertEquals(0, plan2.getEstimatedEntryCount());
+ assertThat(plan2.getEstimatedEntryCount(),
lessThan(plan1.getEstimatedEntryCount()));
assertTrue(pr(plan2).hasProperty("foo"));
assertTrue(pr(plan2).hasProperty("bar"));
@@ -1251,6 +1258,316 @@ public class IndexPlannerTest {
}
//------ END - Suggestion/spellcheck plan tests
+ //------ Cost via doc count per field plan tests
+ @Test
+ public void noRestrictionWithSingleSortableField() throws Exception{
+ NodeBuilder defn = newLucenePropertyIndexDefinition(builder, "test",
of("foo"), "async");
+ defn.setProperty(createProperty(ORDERED_PROP_NAMES, of("foo"),
STRINGS));
+ IndexDefinition definition = new IndexDefinition(root,
defn.getNodeState(), "/test");
+ IndexNode node = createIndexNode(definition);
+ IndexPlanner planner = new IndexPlanner(node, "/test",
createFilter("nt:base"),
+ ImmutableList.of(new OrderEntry("foo", Type.LONG,
OrderEntry.Order.ASCENDING),
+ new OrderEntry("bar", Type.LONG,
OrderEntry.Order.ASCENDING)));
+
+ assertNotNull(planner.getPlan());
+ assertEquals(1, planner.getPlan().getEstimatedEntryCount());
+ assertEquals(definition.getCostPerEntry()/2,
planner.getPlan().getCostPerEntry(), 0.0001);
+ }
+
+ @Test
+ public void noRestrictionWithTwoSortableFields() throws Exception{
+ NodeBuilder defn = newLucenePropertyIndexDefinition(builder, "test",
of("foo", "bar"), "async");
+ defn.setProperty(createProperty(ORDERED_PROP_NAMES, of("foo", "bar"),
STRINGS));
+ IndexDefinition definition = new IndexDefinition(root,
defn.getNodeState(), "/test");
+ IndexNode node = createIndexNode(definition);
+ IndexPlanner planner = new IndexPlanner(node, "/test",
createFilter("nt:base"),
+ ImmutableList.of(new OrderEntry("foo", Type.LONG,
OrderEntry.Order.ASCENDING),
+ new OrderEntry("bar", Type.LONG,
OrderEntry.Order.ASCENDING)));
+
+ assertNotNull(planner.getPlan());
+ assertEquals(1, planner.getPlan().getEstimatedEntryCount());
+ assertEquals(definition.getCostPerEntry()/3,
planner.getPlan().getCostPerEntry(), 0.0001);
+ }
+
+ @Test
+ public void useNumDocsOnFieldForCost() throws Exception {
+ NodeBuilder defn = newLucenePropertyIndexDefinition(builder, "test",
of("foo", "foo1", "foo2"), "async");
+ long numofDocs = IndexDefinition.DEFAULT_ENTRY_COUNT + 1000;
+
+ IndexDefinition idxDefn = new IndexDefinition(root,
defn.getNodeState(), "/test");
+ Document doc = new Document();
+ doc.add(new StringField("foo1", "bar1", Field.Store.NO));
+ Directory sampleDirectory = createSampleDirectory(numofDocs, doc);
+ IndexNode node = createIndexNode(idxDefn, sampleDirectory);
+
+ // Query on "foo"
+ FilterImpl filter = createFilter("nt:base");
+ filter.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("bar"));
+ IndexPlanner planner = new IndexPlanner(node, "/test", filter,
Collections.<OrderEntry>emptyList());
+ QueryIndex.IndexPlan plan = planner.getPlan();
+
+ assertEquals(numofDocs, plan.getEstimatedEntryCount());
+ assertEquals(1.0, plan.getCostPerExecution(), 0);
+ assertEquals(1.0, plan.getCostPerEntry(), 0);
+
+ // Query on "foo1"
+ filter = createFilter("nt:base");
+ filter.restrictProperty("foo1", Operator.EQUAL,
PropertyValues.newString("bar1"));
+ planner = new IndexPlanner(node, "/test", filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ assertEquals(1, plan.getEstimatedEntryCount());
+ assertEquals(1.0, plan.getCostPerExecution(), 0);
+ assertEquals(1.0, plan.getCostPerEntry(), 0);
+
+ // Query on "foo" and "foo1" should use minimum
+ filter = createFilter("nt:base");
+ filter.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("bar"));
+ filter.restrictProperty("foo1", Operator.EQUAL,
PropertyValues.newString("bar1"));
+ planner = new IndexPlanner(node, "/test", filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ assertEquals(1, plan.getEstimatedEntryCount());
+
+ // Query on "foo" and "foo1" and "foo2" should give 0 as foo3 isn't
there in any document
+ filter = createFilter("nt:base");
+ filter.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("bar"));
+ filter.restrictProperty("foo1", Operator.EQUAL,
PropertyValues.newString("bar1"));
+ filter.restrictProperty("foo2", Operator.EQUAL,
PropertyValues.newString("bar2"));
+ planner = new IndexPlanner(node, "/test", filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ assertEquals(0, plan.getEstimatedEntryCount());
+ }
+
+ @Test
+ public void weightedPropDefs() throws Exception {
+ String indexPath = "/test";
+ IndexDefinitionBuilder idxBuilder = new
IndexDefinitionBuilder(child(builder, indexPath));
+
idxBuilder.indexRule("nt:base").property("foo").propertyIndex().weight(500)
+ .enclosingRule().property("foo1").propertyIndex().weight(20)
+ .enclosingRule().property("foo2").propertyIndex().weight(0)
+ .enclosingRule().property("foo3").propertyIndex()
+ ;
+ NodeState defn = idxBuilder.build();
+
+ List<Document> docs = Lists.newArrayList();
+ Document doc;
+ for (int i = 0; i < 60; i++) {
+ doc = new Document();
+ doc.add(new StringField("foo1", "bar1" + i, Field.Store.NO));
+ docs.add(doc);
+ }
+ doc = new Document();
+ doc.add(new StringField("foo2", "bar2", Field.Store.NO));
+ docs.add(doc);
+ Directory sampleDirectory = createSampleDirectory(1000, docs);
+ IndexDefinition idxDefn = new IndexDefinition(root, defn, indexPath);
+ IndexNode node = createIndexNode(idxDefn, sampleDirectory);
+
+ // Query on "foo"
+ FilterImpl filter = createFilter("nt:base");
+ filter.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("bar"));
+ IndexPlanner planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ QueryIndex.IndexPlan plan = planner.getPlan();
+
+ //scale down 1000 by 500 = 2
+ assertEquals(2, plan.getEstimatedEntryCount());
+
+ // Query on "foo1"
+ filter = createFilter("nt:base");
+ filter.restrictProperty("foo1", Operator.EQUAL,
PropertyValues.newString("bar"));
+ planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ //scale down 60 by 20 = 2
+ assertEquals(3, plan.getEstimatedEntryCount());
+
+ // Query on "foo" and "foo1"
+ filter = createFilter("nt:base");
+ filter.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("bar"));
+ filter.restrictProperty("foo1", Operator.EQUAL,
PropertyValues.newString("bar"));
+ planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ //min(2, 3)
+ assertEquals(2, plan.getEstimatedEntryCount());
+
+ // Query on "foo1" and "foo2"
+ filter = createFilter("nt:base");
+ filter.restrictProperty("foo1", Operator.EQUAL,
PropertyValues.newString("bar"));
+ filter.restrictProperty("foo2", Operator.EQUAL,
PropertyValues.newString("bar"));
+ planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ //don't scale down 1 by 0 (foo1 would estimate 3)
+ assertEquals(1, plan.getEstimatedEntryCount());
+
+ // Query on "foo1" and "foo3"
+ filter = createFilter("nt:base");
+ filter.restrictProperty("foo1", Operator.EQUAL,
PropertyValues.newString("bar"));
+ filter.restrictProperty("foo3", Operator.EQUAL,
PropertyValues.newString("bar"));
+ planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ //min(0, 3)
+ assertEquals(0, plan.getEstimatedEntryCount());
+ }
+
+ @Test
+ public void weightedRegexPropDefs() throws Exception {
+ String indexPath = "/test";
+ IndexDefinitionBuilder idxBuilder = new
IndexDefinitionBuilder(child(builder, indexPath));
+ idxBuilder.indexRule("nt:base").property("foo").propertyIndex()
+ .enclosingRule().property("bar", "bar.*",
true).propertyIndex().weight(20)
+ ;
+ NodeState defn = idxBuilder.build();
+
+ List<Document> docs = Lists.newArrayList();
+ Document doc;
+ for (int i = 0; i < 60; i++) {
+ doc = new Document();
+ doc.add(new StringField("bar1", "foo1" + i, Field.Store.NO));
+ docs.add(doc);
+ }
+ for (int i = 0; i < 40; i++) {
+ doc = new Document();
+ doc.add(new StringField("bar2", "foo2" + i, Field.Store.NO));
+ docs.add(doc);
+ }
+ Directory sampleDirectory = createSampleDirectory(1000, docs);
+ IndexDefinition idxDefn = new IndexDefinition(root, defn, indexPath);
+ IndexNode node = createIndexNode(idxDefn, sampleDirectory);
+
+ // Query on and "bar1"
+ FilterImpl filter = createFilter("nt:base");
+ filter.restrictProperty("bar1", Operator.EQUAL,
PropertyValues.newString("foo1"));
+ IndexPlanner planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ QueryIndex.IndexPlan plan = planner.getPlan();
+
+ //scale down 60 by 20 = 3
+ assertEquals(3, plan.getEstimatedEntryCount());
+
+ // Query on and "bar1" and "bar2"
+ filter = createFilter("nt:base");
+ filter.restrictProperty("bar1", Operator.EQUAL,
PropertyValues.newString("foo1"));
+ filter.restrictProperty("bar2", Operator.EQUAL,
PropertyValues.newString("foo2"));
+ planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ //min(3, 2)
+ assertEquals(2, plan.getEstimatedEntryCount());
+ }
+
+ @Test
+ public void overflowingWeight() throws Exception {
+ String indexPath = "/test";
+ IndexDefinitionBuilder idxBuilder = new
IndexDefinitionBuilder(child(builder, indexPath));
+
idxBuilder.indexRule("nt:base").property("foo").propertyIndex().weight(Integer.MAX_VALUE/2)
+ .enclosingRule().property("foo1").propertyIndex()
+ ;
+ NodeState defn = idxBuilder.build();
+
+ List<Document> docs = Lists.newArrayList();
+ Document doc;
+ for (int i = 0; i < 60; i++) {
+ doc = new Document();
+ doc.add(new StringField("foo1", "bar1" + i, Field.Store.NO));
+ docs.add(doc);
+ }
+ Directory sampleDirectory = createSampleDirectory(1000, docs);
+ IndexDefinition idxDefn = new IndexDefinition(root, defn, indexPath);
+ IndexNode node = createIndexNode(idxDefn, sampleDirectory);
+
+ // Query on and "foo"
+ FilterImpl filter = createFilter("nt:base");
+ filter.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("foo1"));
+ IndexPlanner planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ QueryIndex.IndexPlan plan = planner.getPlan();
+
+ //scale down 1000 by INT_MAX/2 and ceil ~= 1.
+ assertEquals(1, plan.getEstimatedEntryCount());
+
+ // Query on and "foo" and "foo1"
+ filter = createFilter("nt:base");
+ filter.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("bar"));
+ filter.restrictProperty("foo1", Operator.EQUAL,
PropertyValues.newString("bar1"));
+ planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ //min(1, 60)
+ assertEquals(1, plan.getEstimatedEntryCount());
+ }
+
+ @Test
+ public void functionPropDef() throws Exception {
+ String indexPath = "/test";
+ IndexDefinitionBuilder idxBuilder = new
IndexDefinitionBuilder(child(builder, indexPath));
+ idxBuilder.indexRule("nt:base").property("foo").propertyIndex();
+ Tree fooPD =
idxBuilder.getBuilderTree().getChild("indexRules").getChild("nt:base")
+ .getChild("properties").getChild("foo");
+ fooPD.setProperty(PROP_FUNCTION, "lower([foo])");
+ NodeState defn = idxBuilder.build();
+
+ Document doc = new Document();
+ doc.add(new StringField(convertToPolishNotation("lower([foo])"),
"bar1", Field.Store.NO));
+ Directory sampleDirectory = createSampleDirectory(2, doc);
+ IndexDefinition idxDefn = new IndexDefinition(root, defn, indexPath);
+ IndexNode node = createIndexNode(idxDefn, sampleDirectory);
+
+ // Query on and "foo"
+ FilterImpl filter = createFilter("nt:base");
+ filter.restrictProperty(convertToPolishNotation("lower([foo])"),
Operator.EQUAL,
+ PropertyValues.newString("foo1"));
+ IndexPlanner planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ QueryIndex.IndexPlan plan = planner.getPlan();
+
+ assertEquals(1, plan.getEstimatedEntryCount());
+ }
+
+ @Test
+ public void fullTextWithPropRestriction() throws Exception{
+ String indexPath = "/test";
+ IndexDefinitionBuilder idxBuilder = new
IndexDefinitionBuilder(child(builder, indexPath));
+ idxBuilder.indexRule("nt:base").property("foo").nodeScopeIndex()
+ .enclosingRule().property("foo1").propertyIndex()
+ .enclosingRule().property("foo2").analyzed();
+ NodeState defn = idxBuilder.build();
+
+ long numofDocs = IndexDefinition.DEFAULT_ENTRY_COUNT + 1000;
+
+ IndexDefinition idxDefn = new IndexDefinition(root, defn, indexPath);
+ Document doc = new Document();
+ doc.add(new StringField("foo1", "bar1", Field.Store.NO));
+ Directory sampleDirectory = createSampleDirectory(numofDocs, doc);
+ IndexNode node = createIndexNode(idxDefn, sampleDirectory);
+
+ // contains(., 'mountain') AND contains('foo2', 'hill')
+ FilterImpl filter = createFilter("nt:base");
+ filter.setFullTextConstraint(FullTextParser.parse(".", "mountain"));
+ filter.setFullTextConstraint(FullTextParser.parse("foo2", "hill"));
+ IndexPlanner planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ QueryIndex.IndexPlan plan = planner.getPlan();
+
+ assertEquals(numofDocs + 1, plan.getEstimatedEntryCount());
+ assertEquals(1.0, plan.getCostPerExecution(), 0);
+ assertEquals(1.0, plan.getCostPerEntry(), 0);
+
+ // contains(., 'mountain') AND [foo1]='bar' AND contains('foo2',
'hill')
+ filter = createFilter("nt:base");
+ filter.setFullTextConstraint(FullTextParser.parse(".", "mountain"));
+ filter.restrictProperty("foo1", Operator.EQUAL,
PropertyValues.newString("bar"));
+ filter.setFullTextConstraint(FullTextParser.parse("foo2", "hill"));
+ planner = new IndexPlanner(node, indexPath, filter,
Collections.<OrderEntry>emptyList());
+ plan = planner.getPlan();
+
+ assertEquals(1, plan.getEstimatedEntryCount());
+ assertEquals(1.0, plan.getCostPerExecution(), 0);
+ assertEquals(1.0, plan.getCostPerEntry(), 0);
+ }
+ //------ END - Cost via doc count per field plan tests
+
+
private IndexNode createIndexNode(IndexDefinition defn, long numOfDocs)
throws IOException {
return new IndexNodeManager("foo", defn, new
TestReaderFactory(createSampleDirectory(numOfDocs)).createReaders(defn,
EMPTY_NODE, "foo"), null).acquire();
}
@@ -1259,6 +1576,10 @@ public class IndexPlannerTest {
return new IndexNodeManager("foo", defn, new
TestReaderFactory(createSampleDirectory()).createReaders(defn, EMPTY_NODE,
"foo"), null).acquire();
}
+ private IndexNode createIndexNode(IndexDefinition defn, Directory
sampleDirectory) throws IOException {
+ return new IndexNodeManager("foo", defn, new
TestReaderFactory(sampleDirectory).createReaders(defn, EMPTY_NODE, "foo"),
null).acquire();
+ }
+
private FilterImpl createFilter(String nodeTypeName) {
NodeTypeInfoProvider nodeTypes = new
NodeStateNodeTypeInfoProvider(root);
NodeTypeInfo type = nodeTypes.getNodeTypeInfo(nodeTypeName);
@@ -1271,6 +1592,14 @@ public class IndexPlannerTest {
}
private static Directory createSampleDirectory(long numOfDocs) throws
IOException {
+ return createSampleDirectory(numOfDocs, Collections.EMPTY_LIST);
+ }
+
+ private static Directory createSampleDirectory(long numOfDocs, @Nonnull
Document doc) throws IOException {
+ return createSampleDirectory(numOfDocs,
Collections.singletonList(doc));
+ }
+
+ private static Directory createSampleDirectory(long numOfDocs,
Iterable<Document> docs) throws IOException {
Directory dir = new RAMDirectory();
IndexWriterConfig config = new IndexWriterConfig(VERSION,
LuceneIndexConstants.ANALYZER);
IndexWriter writer = new IndexWriter(dir, config);
@@ -1279,6 +1608,9 @@ public class IndexPlannerTest {
doc.add(new StringField("foo", "bar" + i, Field.Store.NO));
writer.addDocument(doc);
}
+ for (Document doc : docs) {
+ writer.addDocument(doc);
+ }
writer.close();
return dir;
}
Added:
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatisticsTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatisticsTest.java?rev=1813955&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatisticsTest.java
(added)
+++
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatisticsTest.java
Wed Nov 1 08:49:52 2017
@@ -0,0 +1,167 @@
+/*
+ * 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;
+
+import com.google.common.collect.Lists;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import static
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.VERSION;
+
+public class IndexStatisticsTest {
+
+ @Test
+ public void numDocs() throws Exception {
+ Directory d = createSampleDirectory(2);
+ IndexStatistics stats = getStats(d);
+
+ Assert.assertEquals(2, stats.numDocs());
+ }
+
+ @Test
+ public void numDocsWithDelele() throws Exception {
+ Directory d = createSampleDirectory(2);
+ {
+ IndexWriter writer = getWriter(d);
+ writer.deleteDocuments(new Term("foo", "bar1"));
+ writer.close();
+ }
+
+ IndexStatistics stats = getStats(d);
+ Assert.assertEquals(1, stats.numDocs());
+ }
+
+ @Test
+ public void getSimpleFieldDocCnt() throws Exception {
+ Directory d = createSampleDirectory(2);
+ IndexStatistics stats = getStats(d);
+
+ Assert.assertEquals(2, stats.getDocCountFor("foo"));
+ }
+
+ @Test
+ public void getSimpleFieldDocCntWithDelete() throws Exception {
+ Directory d = createSampleDirectory(2);
+ {
+ IndexWriter writer = getWriter(d);
+ writer.deleteDocuments(new Term("foo", "bar1"));
+ writer.close();
+ }
+
+ IndexStatistics stats = getStats(d);
+ Assert.assertEquals("Stats don't need to get accurate result which
might require reading more",
+ 2, stats.getDocCountFor("foo"));
+ }
+
+ @Test
+ public void absentFields() throws Exception {
+ Directory d = createSampleDirectory(1);
+ IndexStatistics stats = getStats(d);
+
+ Assert.assertEquals(1, stats.getDocCountFor("foo"));
+ Assert.assertEquals(0, stats.getDocCountFor("absent"));
+ Assert.assertEquals(-1, stats.getDocCountFor(":someHiddenField"));
+ Assert.assertEquals(-1,
stats.getDocCountFor(FieldNames.ANALYZED_FIELD_PREFIX + "foo"));
+ Assert.assertEquals(-1,
stats.getDocCountFor(FieldNames.FULLTEXT_RELATIVE_NODE + "foo"));
+ Assert.assertEquals(-1, stats.getDocCountFor("foo_facet"));
+ }
+
+ @Test
+ public void onlyPropertyFields() throws Exception {
+ Document document = new Document();
+ document.add(new StringField("foo", "manualBar", Field.Store.NO));
+ document.add(new StringField(":someHiddenField", "manualBar",
Field.Store.NO));
+ document.add(new StringField(FieldNames.ANALYZED_FIELD_PREFIX + "foo",
"manualBar", Field.Store.NO));
+ document.add(new StringField(FieldNames.FULLTEXT_RELATIVE_NODE +
"foo", "manualBar", Field.Store.NO));
+ document.add(new StringField("foo_facet", "manualBar",
Field.Store.NO));
+ Directory d = createSampleDirectory(document);
+ IndexStatistics stats = getStats(d);
+
+ Assert.assertEquals(3, stats.getDocCountFor("foo"));
+ Assert.assertEquals(0, stats.getDocCountFor("absent"));
+ Assert.assertEquals(-1, stats.getDocCountFor(":someHiddenField"));
+ Assert.assertEquals(-1,
stats.getDocCountFor(FieldNames.ANALYZED_FIELD_PREFIX + "foo"));
+ Assert.assertEquals(-1,
stats.getDocCountFor(FieldNames.FULLTEXT_RELATIVE_NODE + "foo"));
+ Assert.assertEquals(-1, stats.getDocCountFor("foo_facet"));
+ }
+
+ private static Directory createSampleDirectory(long numOfDocs) throws
IOException {
+ return createSampleDirectory(numOfDocs, Lists.newArrayList());
+ }
+
+ private static Directory createSampleDirectory(Document moreDoc) throws
IOException {
+ return createSampleDirectory(2, Collections.singleton(moreDoc));
+ }
+
+ private static Directory createSampleDirectory(long numOfDocs,
Iterable<Document> moreDocs) throws IOException {
+ List<Document> docs = Lists.newArrayList(moreDocs);
+ for (int i = 0; i < numOfDocs; i++) {
+ Document doc = new Document();
+ doc.add(new StringField("foo", "bar" + i, Field.Store.NO));
+ docs.add(doc);
+ }
+
+ return createSampleDirectory(docs);
+ }
+
+ private static Directory createSampleDirectory(Iterable<Document> docs)
throws IOException {
+ Directory dir = new RAMDirectory();
+ IndexWriter writer = null;
+ try {
+ writer = getWriter(dir);
+ for (Document doc : docs) {
+ writer.addDocument(doc);
+ }
+ return dir;
+ } finally {
+ if (writer != null) {
+ writer.close();
+ }
+ }
+ }
+
+ private static IndexWriter getWriter(Directory d) throws IOException {
+ IndexWriterConfig config = new IndexWriterConfig(VERSION,
LuceneIndexConstants.ANALYZER);
+ return new IndexWriter(d, config);
+ }
+
+ private static IndexStatistics getStats(Directory d) throws IOException {
+ IndexReader reader = DirectoryReader.open(d);
+ // no more reads
+ d.close();
+
+ IndexStatistics stats = new IndexStatistics(reader);
+ //close reader... Index stats would read numDocs right away
+ reader.close();
+
+ return stats;
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexStatisticsTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiplexingLucenePropertyIndexTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiplexingLucenePropertyIndexTest.java?rev=1813955&r1=1813954&r2=1813955&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiplexingLucenePropertyIndexTest.java
(original)
+++
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiplexingLucenePropertyIndexTest.java
Wed Nov 1 08:49:52 2017
@@ -71,6 +71,9 @@ import org.apache.jackrabbit.oak.spi.sta
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.StringField;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -136,8 +139,14 @@ public class MultiplexingLucenePropertyI
LuceneIndexWriterFactory factory = new DefaultIndexWriterFactory(mip,
directoryFactory, new LuceneIndexWriterConfig());
LuceneIndexWriter writer = factory.newInstance(defn, builder, true);
- writer.updateDocument("/content/en", newDoc("/content/en"));
- writer.updateDocument("/libs/config", newDoc("/libs/config"));
+ Document doc = newDoc("/content/en");
+ doc.add(new StringField("foo", "bar", Field.Store.NO));
+ writer.updateDocument("/content/en", doc);
+
+ doc = newDoc("/libs/config");
+ doc.add(new StringField("foo", "baz", Field.Store.NO));
+ writer.updateDocument("/libs/config", doc);
+
writer.close(0);
//2. Construct the readers