Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Upgrade Lucene to 10.3.2
SELECT core.executeJavaUpgradeCode('reindex');
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Upgrade Lucene to 10.3.2
EXEC core.executeJavaUpgradeCode 'reindex';
2 changes: 1 addition & 1 deletion search/src/org/labkey/search/SearchMcp.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
public class SearchMcp implements McpService.McpImpl
{
final static String mdSearchHelp = """
The search functionality is implmeneted by Lucene. The query syntax is
The search functionality is implemented by Lucene. The query syntax is

Core Syntax Elements

Expand Down
2 changes: 1 addition & 1 deletion search/src/org/labkey/search/SearchModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public String getName()
@Override
public Double getSchemaVersion()
{
return 26.000;
return 26.001;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@
import org.labkey.api.files.FileSystemDirectoryListener;
import org.labkey.api.files.FileSystemWatchers;
import org.labkey.api.mbean.SearchMXBean;
import org.labkey.api.module.ModuleLoader;
import org.labkey.api.portal.ProjectUrls;
import org.labkey.api.resource.Resource;
import org.labkey.api.search.SearchService;
Expand Down Expand Up @@ -114,7 +113,6 @@
import org.labkey.api.util.logging.LogHelper;
import org.labkey.api.view.ActionURL;
import org.labkey.api.view.UnauthorizedException;
import org.labkey.api.view.WebPartView;
import org.labkey.api.webdav.FileSystemResource;
import org.labkey.api.webdav.SimpleDocumentResource;
import org.labkey.api.webdav.WebdavResource;
Expand Down Expand Up @@ -1428,7 +1426,7 @@ private long getDocCount(Query query) throws IOException
try
{
TopDocs docs = searcher.search(query, 1);
return docs.totalHits.value;
return docs.totalHits.value(); // TODO: This may not be exact, could be a lower bound. See totalHits.relation().
}
finally
{
Expand Down Expand Up @@ -1842,7 +1840,7 @@ private void processSearchResult(int offset, int hitsToRetrieve, TopDocs topDocs
}

result.offset = offset;
result.totalHits = topDocs.totalHits.value;
result.totalHits = topDocs.totalHits.value(); // TODO: This may not be exact, could be a lower bound. See totalHits.relation().
result.hits = ret;
}

Expand Down
116 changes: 74 additions & 42 deletions search/src/org/labkey/search/model/SecurityQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
import org.apache.lucene.search.QueryVisitor;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.ScorerSupplier;
import org.apache.lucene.search.Weight;
import org.apache.lucene.util.BitSetIterator;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.FixedBitSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.labkey.api.data.Container;
import org.labkey.api.data.ContainerManager;
import org.labkey.api.module.Module;
Expand Down Expand Up @@ -96,64 +98,94 @@ public boolean isCacheable(LeafReaderContext ctx)

private boolean isReadable(String containerId, String categories)
{
// return _containerIds.containsKey(containerId);
if (StringUtils.isEmpty(categories) || !_categoryContainers.containsKey(categories))
return _containerIds.containsKey(containerId);
else
return _categoryContainers.get(categories).contains(containerId);
}

@Override
public Scorer scorer(LeafReaderContext context) throws IOException
public ScorerSupplier scorerSupplier(LeafReaderContext context)
{
SearchService.SEARCH_PHASE currentPhase = _iTimer.getCurrentPhase();
_iTimer.setPhase(SearchService.SEARCH_PHASE.applySecurityFilter);

LeafReader reader = context.reader();
int maxDoc = reader.maxDoc();
FixedBitSet bits = new FixedBitSet(maxDoc);
return new ScorerSupplier()
{
private final LeafReader _reader;
private final @Nullable BinaryDocValues _securityContextDocValues;

BinaryDocValues securityContextDocValues = reader.getBinaryDocValues(FIELD_NAME.securityContext.name());
{
SearchService.SEARCH_PHASE currentPhase = _iTimer.getCurrentPhase();

try
{
int doc;
try
{
_iTimer.setPhase(SearchService.SEARCH_PHASE.applySecurityFilter);
_reader = context.reader();
_securityContextDocValues = _reader.getBinaryDocValues(FIELD_NAME.securityContext.name());
}
catch (IOException e)
{
throw new RuntimeException(e);
}
finally
{
_iTimer.setPhase(currentPhase);
}
}

// Can be null, if no documents (e.g., shortly after bootstrap or clear index)
if (null != securityContextDocValues)
@Override
public Scorer get(long leadCost) throws IOException
{
while (NO_MORE_DOCS != (doc = securityContextDocValues.nextDoc()))
SearchService.SEARCH_PHASE currentPhase = _iTimer.getCurrentPhase();

try
{
BytesRef bytesRef = securityContextDocValues.binaryValue();
String securityContext = StringUtils.trimToNull(bytesRef.utf8ToString());

final String containerId;
final String resourceId;
final String categories;
String[] parts = StringUtils.split(securityContext, "|");
// SecurityContext is usually just a container ID and a string of categories, but in some cases it adds a resource ID.
containerId = parts[0];
if (parts.length > 1)
categories = parts[1];
else
categories = null;
if (parts.length > 2)
resourceId = parts[2];
else
resourceId = null;

// Must have read permission on the container (always). Must also have read permissions on resource ID, if non-null.
if (isReadable(containerId, categories) && (null == resourceId || canReadResource(resourceId, containerId)))
bits.set(doc);
_iTimer.setPhase(SearchService.SEARCH_PHASE.applySecurityFilter);
int maxDoc = _reader.maxDoc();
FixedBitSet bits = new FixedBitSet(maxDoc);
int doc;

// Can be null, if no documents (e.g., shortly after bootstrap or clear index)
if (null != _securityContextDocValues)
{
while (NO_MORE_DOCS != (doc = _securityContextDocValues.nextDoc()))
{
BytesRef bytesRef = _securityContextDocValues.binaryValue();
String securityContext = StringUtils.trimToNull(bytesRef.utf8ToString());

final String containerId;
final String resourceId;
final String categories;
String[] parts = StringUtils.split(securityContext, "|");
// SecurityContext is usually just a container ID and a string of categories, but in some cases it adds a resource ID.
containerId = parts[0];
if (parts.length > 1)
categories = parts[1];
else
categories = null;
if (parts.length > 2)
resourceId = parts[2];
else
resourceId = null;

// Must have read permission on the container (always). Must also have read permissions on resource ID, if non-null.
if (isReadable(containerId, categories) && (null == resourceId || canReadResource(resourceId, containerId)))
bits.set(doc);
}
}

return new ConstantScoreScorer(score(), scoreMode, new BitSetIterator(bits, bits.approximateCardinality()));
}
finally
{
_iTimer.setPhase(currentPhase);
}
}

return new ConstantScoreScorer(this, score(), scoreMode, new BitSetIterator(bits, bits.approximateCardinality()));
}
finally
{
_iTimer.setPhase(currentPhase);
}
@Override
public long cost()
{
return null == _securityContextDocValues ? 0 : _securityContextDocValues.cost();
}
};
}
};
}
Expand Down