xiangfu0 commented on code in PR #19760:
URL: https://github.com/apache/pinot/pull/19760#discussion_r4193808134
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexReader.java:
##########
@@ -255,6 +278,64 @@ private MutableRoaringBitmap
getDocIdsWithoutOptions(String searchQuery) {
}
}
+ @Override
+ public int getNumMatchingDocs(String searchQuery, @Nullable String
optionsString) {
+ try {
+ Query query = buildQuery(searchQuery, optionsString);
+ int count = countWithoutMaterializing(query);
+ if (count >= 0) {
+ return count;
+ }
+ // Lucene cannot count this query from metadata. Collect instead,
reusing the query already built and the
+ // collector that carries the query-termination check, so a slow count
still honours the query timeout.
+ MutableRoaringBitmap docIds = new MutableRoaringBitmap();
+ _indexSearcher.search(query, new LuceneDocIdCollector(docIds,
_docIdTranslator));
+ return docIds.getCardinality();
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Caught exception while counting matches in the text index for
column:" + _column + " search query:"
+ + searchQuery, e);
+ }
+ }
+
+ /// Counts matches from index metadata alone, without visiting any document.
+ ///
+ /// Lucene can do this for some queries -- a single term in particular --
and reports that it cannot by
+ /// returning -1 for a leaf, in which case this returns -1 and the caller
collects instead.
+ ///
+ /// @return the match count, or -1 if any leaf cannot be counted without
materializing matches
+ @VisibleForTesting
+ int countWithoutMaterializing(Query query)
Review Comment:
`countWithoutMaterializing` bypasses `_docIdTranslator`, so a
Lucene-docs/Pinot-docs mismatch now yields a silently wrong `COUNT(*)` where
the old path failed loudly.
This is reproducible: on an index whose Lucene doc count exceeds the Pinot
segment's `numDocs` (reachable today via on-disk segments converted by
pre-#18885 Pinot after a crashed conversion retry), `getDocIds` throws
`IndexOutOfBoundsException` from `DefaultDocIdTranslator`, but this fast path
returns the inflated raw Lucene count (e.g. 8 on an index where `numDocs=4`).
That turns detectable corruption into a wrong answer.
Cheap hardening: skip the fast path (or fail) when `_indexReader.numDocs() >
numDocs`.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexReader.java:
##########
@@ -255,6 +278,64 @@ private MutableRoaringBitmap
getDocIdsWithoutOptions(String searchQuery) {
}
}
+ @Override
+ public int getNumMatchingDocs(String searchQuery, @Nullable String
optionsString) {
+ try {
+ Query query = buildQuery(searchQuery, optionsString);
+ int count = countWithoutMaterializing(query);
Review Comment:
Every non-countable query shape (phrases, booleans,
wildcard/prefix/fuzzy/regexp — common TEXT_MATCH shapes) now pays query rewrite
+ `createWeight` twice per `count(*)`: the probe on `_countSearcher` is thrown
away on `-1`, and the fallback `_indexSearcher.search` rewrites from scratch.
For e.g. `count(*) WHERE TEXT_MATCH(col, 'conn*')`, the multi-term rewrite
enumerates the terms dictionary once in `countWithoutMaterializing` and again
inside the fallback search — making these counts measurably **slower** than
before this PR.
Lucene's own `IndexSearcher#count` avoids this with a
`TotalHitCountCollector`-style design: one search, `weight.count(ctx)` probed
per leaf inside `getLeafCollector` (throwing `CollectionTerminatedException` on
countable leaves, counting collection otherwise). That gives one rewrite, one
`Weight`, per-leaf hybrid counting — and no second searcher needed. Adopting it
here would also resolve the `_countSearcher` duplication and the fallback's
bitmap materialization (see other comments).
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexReader.java:
##########
@@ -67,6 +72,11 @@ public class LuceneTextIndexReader implements
TextIndexReader {
private final IndexReader _indexReader;
private final Directory _indexDirectory;
private final IndexSearcher _indexSearcher;
+ /// Used only by [#getNumMatchingDocs]. Deliberately never query-cached: the
caching policy counts
+ /// every Weight created, so probing on the shared searcher and then falling
back to a search would
+ /// register two uses for one query and promote entries into
[org.apache.lucene.search.LRUQueryCache]
+ /// at twice the intended rate -- the heap behaviour the query cache is
disabled by default to avoid.
+ private final IndexSearcher _countSearcher;
Review Comment:
A few issues with the dedicated `_countSearcher`:
- In the default config (query cache already disabled) it's a byte-for-byte
duplicate of `_indexSearcher`.
- For users who set `enableQueryCache=true`, it unconditionally forfeits
cached counts (Lucene 9.12's `CachingWrapperWeight.count` serves cached
`CacheAndCount` entries), so repeated counts get strictly slower than before
the PR.
- The justifying comment misstates the mechanism:
`UsageTrackingQueryCachingPolicy.onUse` fires per *use* of a weight, not per
`createWeight`.
- Two searcher fields must now be kept in sync across both constructors for
any future searcher-level setting, or the count path silently diverges from the
filter path.
Simpler: `_countSearcher = config.isEnableQueryCache() ? uncachedSearcher :
_indexSearcher` — or eliminate the field entirely via the single-pass
counting-collector design (see comment on `getNumMatchingDocs`), which creates
exactly one `Weight`.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexReader.java:
##########
@@ -255,6 +278,64 @@ private MutableRoaringBitmap
getDocIdsWithoutOptions(String searchQuery) {
}
}
+ @Override
+ public int getNumMatchingDocs(String searchQuery, @Nullable String
optionsString) {
+ try {
+ Query query = buildQuery(searchQuery, optionsString);
+ int count = countWithoutMaterializing(query);
+ if (count >= 0) {
+ return count;
+ }
+ // Lucene cannot count this query from metadata. Collect instead,
reusing the query already built and the
+ // collector that carries the query-termination check, so a slow count
still honours the query timeout.
+ MutableRoaringBitmap docIds = new MutableRoaringBitmap();
+ _indexSearcher.search(query, new LuceneDocIdCollector(docIds,
_docIdTranslator));
+ return docIds.getCardinality();
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Caught exception while counting matches in the text index for
column:" + _column + " search query:"
+ + searchQuery, e);
+ }
+ }
+
+ /// Counts matches from index metadata alone, without visiting any document.
+ ///
+ /// Lucene can do this for some queries -- a single term in particular --
and reports that it cannot by
+ /// returning -1 for a leaf, in which case this returns -1 and the caller
collects instead.
+ ///
+ /// @return the match count, or -1 if any leaf cannot be counted without
materializing matches
+ @VisibleForTesting
+ int countWithoutMaterializing(Query query)
+ throws IOException {
+ Weight weight = _countSearcher.createWeight(_countSearcher.rewrite(new
ConstantScoreQuery(query)),
+ ScoreMode.COMPLETE_NO_SCORES, 1f);
+ int total = 0;
+ for (LeafReaderContext leaf : _indexReader.leaves()) {
+ int leafCount = weight.count(leaf);
+ if (leafCount == -1) {
+ return -1;
+ }
+ total += leafCount;
+ }
+ return total;
+ }
+
+ /// Parses a search string into a Lucene query, honouring the options string
when one is given.
+ ///
+ /// Shared by the doc-id and the count paths so that a `count(*)` and the
filter it counts can never
Review Comment:
This javadoc claims the method is "Shared by the doc-id and the count
paths", but the doc-id path never calls it — `getDocIds(String, String)`
performs the same options dispatch independently. So the invariant the comment
promises (count and filter can never interpret a query differently) is not
actually enforced: a maintainer could edit options handling here, trust the
comment, and skip `getDocIds` — shipping exactly the count-vs-filter divergence
this comment says is impossible. `LuceneTextIndexCountTest` can't catch that,
because the fallback uses `buildQuery` too, making the comparison a tautology.
Suggest routing `getDocIds(String, String)` through `buildQuery(searchQuery,
optionsString)` (which would also collapse
`getDocIdsWithOptions`/`getDocIdsWithoutOptions`), or correcting the comments
on both overloads (the 1-arg overload's comment also says it applies "options",
which it doesn't).
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexReader.java:
##########
@@ -255,6 +278,64 @@ private MutableRoaringBitmap
getDocIdsWithoutOptions(String searchQuery) {
}
}
+ @Override
+ public int getNumMatchingDocs(String searchQuery, @Nullable String
optionsString) {
+ try {
+ Query query = buildQuery(searchQuery, optionsString);
+ int count = countWithoutMaterializing(query);
+ if (count >= 0) {
+ return count;
+ }
+ // Lucene cannot count this query from metadata. Collect instead,
reusing the query already built and the
Review Comment:
The fallback still materializes the full doc-id bitmap plus a per-hit
`DocIdTranslator` lookup just to read `getCardinality()` — the exact per-match
cost this PR exists to remove — and is a third in-class copy of the bitmap +
`LuceneDocIdCollector` + search idiom. A count-only `LeafCollector` keeping the
same `QueryThreadContext.checkTerminationAndSampleUsagePeriodically` check but
incrementing an `int` preserves the timeout behaviour and skips both;
translation is cardinality-invariant (1:1 mapping).
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TextMatchFilterOperator.java:
##########
@@ -79,10 +79,13 @@ public boolean canOptimizeCount() {
@Override
public int getNumMatchingDocs() {
if (_textIndexReader.isMultiColumn()) {
+ // TODO: give MultiColumnTextIndexReader the same count short-circuit.
return ((MultiColumnTextIndexReader)
_textIndexReader).getDocIds(_column, _predicate.getValue(),
_predicate.getOptions()).getCardinality();
} else {
Review Comment:
Rather than an operator-side TODO, a two-line default method on
`MultiColumnTextIndexReader` — `getNumMatchingDocs(column, query, options)`
defaulting to `getDocIds(...).getCardinality()` — would put both branches at
the same altitude, so completing the TODO only touches the reader, not this
operator again.
Also a latent landmine worth noting: `MultiColumnLuceneTextIndexReader`
inherits the new single-column `getNumMatchingDocs` default, whose delegate
`getDocIds` throws `UnsupportedOperationException("Multi-column text index
requires column name to query!")` if any future caller invokes it generically.
##########
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexCountTest.java:
##########
@@ -0,0 +1,166 @@
+/**
+ * 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.pinot.segment.local.segment.index.readers.text;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import org.apache.commons.io.FileUtils;
+import
org.apache.pinot.segment.local.segment.creator.impl.text.LuceneTextIndexCreator;
+import
org.apache.pinot.segment.local.segment.index.text.TextIndexConfigBuilder;
+import org.apache.pinot.segment.spi.index.TextIndexConfig;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+
+/// Pins the invariant behind the `count(*)` short-circuit:
[LuceneTextIndexReader#getNumMatchingDocs] must
+/// return exactly what counting the materialized doc ids would return, for
every query shape.
+///
+/// The short-circuit reads a count from Lucene's index metadata where it can
and falls back to materializing
+/// doc ids where it cannot, so the two paths have to agree or a `count(*)`
would silently disagree with the
+/// rows its own filter returns.
+public class LuceneTextIndexCountTest {
+ private static final File INDEX_DIR =
Review Comment:
`INDEX_DIR` is a fixed, non-unique path under the shared system temp
directory. Two concurrent executions on the same host (parallel CI executors or
surefire forks sharing `/tmp`) race: one JVM's `@BeforeClass` `deleteDirectory`
removes the other's live mmapped index mid-test, causing intermittent
`NoSuchFileException`/wrong-count flakes. Suggest appending a UUID/nanoTime
suffix as other Pinot index tests do.
Minor, while here: the class javadoc describes behavior but omits a
thread-safety statement (repo convention for new classes), which is relevant
given the static shared `INDEX_DIR`.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexReader.java:
##########
@@ -255,6 +278,64 @@ private MutableRoaringBitmap
getDocIdsWithoutOptions(String searchQuery) {
}
}
+ @Override
+ public int getNumMatchingDocs(String searchQuery, @Nullable String
optionsString) {
+ try {
+ Query query = buildQuery(searchQuery, optionsString);
+ int count = countWithoutMaterializing(query);
+ if (count >= 0) {
+ return count;
+ }
+ // Lucene cannot count this query from metadata. Collect instead,
reusing the query already built and the
+ // collector that carries the query-termination check, so a slow count
still honours the query timeout.
+ MutableRoaringBitmap docIds = new MutableRoaringBitmap();
+ _indexSearcher.search(query, new LuceneDocIdCollector(docIds,
_docIdTranslator));
+ return docIds.getCardinality();
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Caught exception while counting matches in the text index for
column:" + _column + " search query:"
+ + searchQuery, e);
+ }
+ }
+
+ /// Counts matches from index metadata alone, without visiting any document.
+ ///
+ /// Lucene can do this for some queries -- a single term in particular --
and reports that it cannot by
+ /// returning -1 for a leaf, in which case this returns -1 and the caller
collects instead.
+ ///
+ /// @return the match count, or -1 if any leaf cannot be counted without
materializing matches
+ @VisibleForTesting
+ int countWithoutMaterializing(Query query)
+ throws IOException {
+ Weight weight = _countSearcher.createWeight(_countSearcher.rewrite(new
ConstantScoreQuery(query)),
Review Comment:
Two notes on this line:
1. The `ConstantScoreQuery` wrap before rewrite looks redundant next to
`ScoreMode.COMPLETE_NO_SCORES`, but it's load-bearing — it mirrors Lucene's
`IndexSearcher#count` recipe for producing count-friendly weights. Nothing in
the code says so, and a future cleanup that removes the "redundant" wrap
wouldn't fail any test: some rewritten shapes would just stop producing
countable weights and silently degrade to full collection (which
`testFallbackPathIsExercised` is explicitly designed not to catch). Worth one
comment line pointing at `IndexSearcher#count`.
2. Minor: the metadata-count probe (rewrite + `createWeight` + per-leaf
`Weight.count`) runs outside Pinot's query-termination and resource-usage
accounting, so the in-code claim that a slow count "still honours the query
timeout" is only true of the fallback half. A regexp or leading-wildcard count
over a huge terms dictionary does bounded but non-trivial per-leaf term
enumeration during rewrite that the query killer can't interrupt. Low severity
— the old path's rewrite inside `search()` was equally unchecked — but the
probe doubles that unaccounted work for non-countable shapes.
##########
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexCountTest.java:
##########
@@ -0,0 +1,166 @@
+/**
+ * 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.pinot.segment.local.segment.index.readers.text;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import org.apache.commons.io.FileUtils;
+import
org.apache.pinot.segment.local.segment.creator.impl.text.LuceneTextIndexCreator;
+import
org.apache.pinot.segment.local.segment.index.text.TextIndexConfigBuilder;
+import org.apache.pinot.segment.spi.index.TextIndexConfig;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+
+/// Pins the invariant behind the `count(*)` short-circuit:
[LuceneTextIndexReader#getNumMatchingDocs] must
+/// return exactly what counting the materialized doc ids would return, for
every query shape.
+///
+/// The short-circuit reads a count from Lucene's index metadata where it can
and falls back to materializing
+/// doc ids where it cannot, so the two paths have to agree or a `count(*)`
would silently disagree with the
+/// rows its own filter returns.
+public class LuceneTextIndexCountTest {
+ private static final File INDEX_DIR =
+ new File(FileUtils.getTempDirectory(),
LuceneTextIndexCountTest.class.getSimpleName());
+ private static final String COLUMN = "body";
+ /// Every document carries `sentinelall` so there is a term matching all
documents. `to` cannot serve that
+ /// role: it is in Pinot's default English stop-word set and matches nothing.
+ private static final String[] DOCS = {
+ "failed to place order for user alice sentinelall",
+ "failed to charge card for user bob sentinelall",
+ "connection refused by payment service sentinelall",
+ "connection established to cache service sentinelall",
+ "order confirmation email sent to carol sentinelall",
+ "cache miss while loading order details sentinelall",
+ "payment accepted for order 4815 sentinelall",
+ "unrelated housekeeping log line sentinelall"
+ };
+
+ @BeforeClass
+ public void setUp()
+ throws IOException {
+ FileUtils.deleteDirectory(INDEX_DIR);
+ FileUtils.forceMkdir(INDEX_DIR);
+ TextIndexConfig config = new TextIndexConfigBuilder().build();
+ try (LuceneTextIndexCreator creator = new LuceneTextIndexCreator(COLUMN,
INDEX_DIR, true, false, null, null,
+ config)) {
+ for (String doc : DOCS) {
+ creator.add(doc);
+ }
+ creator.seal();
Review Comment:
This fixture is always a single-leaf index: `LuceneTextIndexCreator.seal()`
calls `forceMerge(1)`. That means `countWithoutMaterializing`'s only
non-trivial logic — cross-leaf accumulation and the mid-loop `return -1` — is
never exercised by any test. A regression in the leaf loop (`break`/`continue`
instead of `return -1`, or dropped accumulation) would pass this whole suite
and then return a partial sum instead of falling back on any multi-leaf index,
producing a silently short `count(*)`.
Suggest adding a fixture built via `IndexWriter` with commits between
batches (no force-merge) so `leaves() > 1`, asserting both the summed count and
the `-1` bail.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexReader.java:
##########
@@ -227,26 +241,35 @@ public MutableRoaringBitmap getDocIds(String searchQuery,
@Nullable String optio
return getDocIdsWithoutOptions(searchQuery);
}
+ /// Parses a search string into a Lucene query, applying the configured
parser and options.
+ ///
+ /// Shared by the doc-id and the count paths so that a `count(*)` and the
filter it counts can never
+ /// interpret the same query differently.
+ private Query buildQuery(String searchQuery)
Review Comment:
The count short-circuit lives as a private method in one of four
near-identical Lucene readers; `RealtimeLuceneTextIndex`,
`MultiColumnLuceneTextIndexReader`, and `MultiColumnRealtimeLuceneTextIndex`
keep materializing, so the optimization is largely invisible on realtime-heavy
and multi-column tables (a hybrid table's `count(*)` stays dominated by
consuming segments' full bitmap builds, plus per-doc stored-field lookups in
`RealtimeLuceneTextIndex.getPinotDocIds`). A static
`LuceneTextIndexUtils.countWithoutMaterializing(IndexSearcher, Query)` would
let all four readers adopt it in a few lines, since a count never needs doc-id
translation — the very duplication the TODO at the bottom of this file
documents.
##########
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/text/LuceneTextIndexCountTest.java:
##########
@@ -0,0 +1,166 @@
+/**
+ * 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.pinot.segment.local.segment.index.readers.text;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import org.apache.commons.io.FileUtils;
+import
org.apache.pinot.segment.local.segment.creator.impl.text.LuceneTextIndexCreator;
+import
org.apache.pinot.segment.local.segment.index.text.TextIndexConfigBuilder;
+import org.apache.pinot.segment.spi.index.TextIndexConfig;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+
+/// Pins the invariant behind the `count(*)` short-circuit:
[LuceneTextIndexReader#getNumMatchingDocs] must
+/// return exactly what counting the materialized doc ids would return, for
every query shape.
+///
+/// The short-circuit reads a count from Lucene's index metadata where it can
and falls back to materializing
+/// doc ids where it cannot, so the two paths have to agree or a `count(*)`
would silently disagree with the
+/// rows its own filter returns.
+public class LuceneTextIndexCountTest {
+ private static final File INDEX_DIR =
+ new File(FileUtils.getTempDirectory(),
LuceneTextIndexCountTest.class.getSimpleName());
+ private static final String COLUMN = "body";
+ /// Every document carries `sentinelall` so there is a term matching all
documents. `to` cannot serve that
+ /// role: it is in Pinot's default English stop-word set and matches nothing.
+ private static final String[] DOCS = {
+ "failed to place order for user alice sentinelall",
+ "failed to charge card for user bob sentinelall",
+ "connection refused by payment service sentinelall",
+ "connection established to cache service sentinelall",
+ "order confirmation email sent to carol sentinelall",
+ "cache miss while loading order details sentinelall",
+ "payment accepted for order 4815 sentinelall",
+ "unrelated housekeeping log line sentinelall"
+ };
+
+ @BeforeClass
+ public void setUp()
+ throws IOException {
+ FileUtils.deleteDirectory(INDEX_DIR);
+ FileUtils.forceMkdir(INDEX_DIR);
+ TextIndexConfig config = new TextIndexConfigBuilder().build();
+ try (LuceneTextIndexCreator creator = new LuceneTextIndexCreator(COLUMN,
INDEX_DIR, true, false, null, null,
+ config)) {
+ for (String doc : DOCS) {
+ creator.add(doc);
+ }
+ creator.seal();
+ }
+ }
+
+ @AfterClass
+ public void tearDown()
+ throws IOException {
+ FileUtils.deleteDirectory(INDEX_DIR);
+ }
+
+ @DataProvider(name = "queries")
+ public Object[][] queries() {
+ return new Object[][]{
+ // Single terms: the shape Lucene can count from metadata alone, i.e.
the case the optimization targets.
+ {"order", null},
+ {"connection", null},
+ {"housekeeping", null},
+ // No match, and a term in every document: the boundary cases.
+ {"nonexistentterm", null},
+ {"sentinelall", null},
+ // A stop word, which the default analyzer drops entirely.
+ {"to", null},
+ // Boolean, phrase and multi-term shapes, which generally take the
fallback.
+ {"failed AND order", null},
+ {"failed OR connection", null},
+ {"order AND NOT failed", null},
+ {"\"place order\"", null},
+ {"\"failed to place order\"", null},
+ // Prefix, wildcard, fuzzy and regexp.
+ {"conn*", null},
+ {"c?che", null},
+ {"connection~1", null},
+ {"/conn.*/", null},
+ // Option-driven parsing must agree too.
+ {"failed order connection", "parser=MATCH,minimumShouldMatch=2"},
+ {"*tion", "allowLeadingWildcard=true"}
+ };
+ }
+
+ @Test(dataProvider = "queries")
+ public void testCountMatchesMaterializedDocIds(String query, String options)
+ throws IOException {
+ try (LuceneTextIndexReader reader = new LuceneTextIndexReader(COLUMN,
INDEX_DIR, DOCS.length, new HashMap<>())) {
Review Comment:
Nit: `new HashMap<>()` (here and in the other three tests) can be `Map.of()`
per the repo convention — the value flows only to
`TextIndexConfigBuilder.withProperties()`, which never mutates it.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]