apply new code formatter and save action
This commit is contained in:
@@ -5,60 +5,60 @@ import org.lucares.pdb.blockstorage.BSFile;
|
||||
import org.lucares.pdb.datastore.internal.ParititionId;
|
||||
|
||||
public class Doc {
|
||||
private final Tags tags;
|
||||
private final Tags tags;
|
||||
|
||||
/**
|
||||
* the block number used by {@link BSFile}
|
||||
*/
|
||||
private final long rootBlockNumber;
|
||||
/**
|
||||
* the block number used by {@link BSFile}
|
||||
*/
|
||||
private final long rootBlockNumber;
|
||||
|
||||
private ParititionId partitionId;
|
||||
private ParititionId partitionId;
|
||||
|
||||
/**
|
||||
* Initializes a new document.
|
||||
* <p>
|
||||
* The path can be {@code null}. If path is {@code null}, then
|
||||
* {@code offsetInListingFile} must be set. The path will be initialized lazily
|
||||
* when needed.
|
||||
* <p>
|
||||
* This is used to reduce the memory footprint.
|
||||
*
|
||||
* @param tags
|
||||
* @param offsetInListingFile must be set if {@code path} is {@code null}
|
||||
* @param storageBasePath the storage base path.
|
||||
* @param relativePath optional, can be {@code null}. This path is
|
||||
* relative to {@code storageBasePath}
|
||||
*/
|
||||
public Doc(final ParititionId partitionId, final Tags tags, final long rootBlockNumber) {
|
||||
this.partitionId = partitionId;
|
||||
this.tags = tags;
|
||||
this.rootBlockNumber = rootBlockNumber;
|
||||
}
|
||||
/**
|
||||
* Initializes a new document.
|
||||
* <p>
|
||||
* The path can be {@code null}. If path is {@code null}, then
|
||||
* {@code offsetInListingFile} must be set. The path will be initialized lazily
|
||||
* when needed.
|
||||
* <p>
|
||||
* This is used to reduce the memory footprint.
|
||||
*
|
||||
* @param tags
|
||||
* @param offsetInListingFile must be set if {@code path} is {@code null}
|
||||
* @param storageBasePath the storage base path.
|
||||
* @param relativePath optional, can be {@code null}. This path is
|
||||
* relative to {@code storageBasePath}
|
||||
*/
|
||||
public Doc(final ParititionId partitionId, final Tags tags, final long rootBlockNumber) {
|
||||
this.partitionId = partitionId;
|
||||
this.tags = tags;
|
||||
this.rootBlockNumber = rootBlockNumber;
|
||||
}
|
||||
|
||||
public ParititionId getPartitionId() {
|
||||
return partitionId;
|
||||
}
|
||||
public ParititionId getPartitionId() {
|
||||
return partitionId;
|
||||
}
|
||||
|
||||
public Tags getTags() {
|
||||
return tags;
|
||||
}
|
||||
public Tags getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* the block number used by {@link BSFile}
|
||||
*
|
||||
* @return the root block number of this document
|
||||
*/
|
||||
public long getRootBlockNumber() {
|
||||
return rootBlockNumber;
|
||||
}
|
||||
/**
|
||||
* the block number used by {@link BSFile}
|
||||
*
|
||||
* @return the root block number of this document
|
||||
*/
|
||||
public long getRootBlockNumber() {
|
||||
return rootBlockNumber;
|
||||
}
|
||||
|
||||
public void setPartitionId(final ParititionId partitionId) {
|
||||
this.partitionId = partitionId;
|
||||
}
|
||||
public void setPartitionId(final ParititionId partitionId) {
|
||||
this.partitionId = partitionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Doc [partitionId=" + partitionId + ", tags=" + tags + ", rootBlockNumber=" + rootBlockNumber + "]";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Doc [partitionId=" + partitionId + ", tags=" + tags + ", rootBlockNumber=" + rootBlockNumber + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.lucares.pdb.datastore;
|
||||
|
||||
public class InvalidValueException extends IllegalArgumentException {
|
||||
|
||||
private static final long serialVersionUID = -8707541995666127297L;
|
||||
private static final long serialVersionUID = -8707541995666127297L;
|
||||
|
||||
public InvalidValueException(final String msg) {
|
||||
super(msg);
|
||||
}
|
||||
public InvalidValueException(final String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,86 +14,86 @@ import org.lucares.pdb.diskstorage.DiskStorage;
|
||||
|
||||
public class PdbFile {
|
||||
|
||||
private static class PdbFileToLongStream implements Function<PdbFile, Stream<LongList>> {
|
||||
private static class PdbFileToLongStream implements Function<PdbFile, Stream<LongList>> {
|
||||
|
||||
private final PartitionDiskStore partitionDiskStorage;
|
||||
private final PartitionDiskStore partitionDiskStorage;
|
||||
|
||||
public PdbFileToLongStream(final PartitionDiskStore partitionDiskStorage) {
|
||||
this.partitionDiskStorage = partitionDiskStorage;
|
||||
}
|
||||
public PdbFileToLongStream(final PartitionDiskStore partitionDiskStorage) {
|
||||
this.partitionDiskStorage = partitionDiskStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<LongList> apply(final PdbFile pdbFile) {
|
||||
final DiskStorage diskStorage = partitionDiskStorage.getExisting(pdbFile.getPartitionId());
|
||||
final TimeSeriesFile bsFile = TimeSeriesFile.existingFile(pdbFile.getRootBlockNumber(), diskStorage);
|
||||
return bsFile.streamOfLongLists();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Stream<LongList> apply(final PdbFile pdbFile) {
|
||||
final DiskStorage diskStorage = partitionDiskStorage.getExisting(pdbFile.getPartitionId());
|
||||
final TimeSeriesFile bsFile = TimeSeriesFile.existingFile(pdbFile.getRootBlockNumber(), diskStorage);
|
||||
return bsFile.streamOfLongLists();
|
||||
}
|
||||
}
|
||||
|
||||
private final Tags tags;
|
||||
private final Tags tags;
|
||||
|
||||
/**
|
||||
* The rootBlockNumber to be used by {@link BSFile}
|
||||
*/
|
||||
private final long rootBlockNumber;
|
||||
/**
|
||||
* The rootBlockNumber to be used by {@link BSFile}
|
||||
*/
|
||||
private final long rootBlockNumber;
|
||||
|
||||
private final ParititionId partitionId;
|
||||
private final ParititionId partitionId;
|
||||
|
||||
public PdbFile(final ParititionId partitionId, final long rootBlockNumber, final Tags tags) {
|
||||
this.partitionId = partitionId;
|
||||
this.rootBlockNumber = rootBlockNumber;
|
||||
this.tags = tags;
|
||||
}
|
||||
public PdbFile(final ParititionId partitionId, final long rootBlockNumber, final Tags tags) {
|
||||
this.partitionId = partitionId;
|
||||
this.rootBlockNumber = rootBlockNumber;
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
public Tags getTags() {
|
||||
return tags;
|
||||
}
|
||||
public Tags getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public long getRootBlockNumber() {
|
||||
return rootBlockNumber;
|
||||
}
|
||||
public long getRootBlockNumber() {
|
||||
return rootBlockNumber;
|
||||
}
|
||||
|
||||
public ParititionId getPartitionId() {
|
||||
return partitionId;
|
||||
}
|
||||
public ParititionId getPartitionId() {
|
||||
return partitionId;
|
||||
}
|
||||
|
||||
public static Stream<LongList> toStream(final List<PdbFile> pdbFiles, final PartitionDiskStore diskStorage) {
|
||||
public static Stream<LongList> toStream(final List<PdbFile> pdbFiles, final PartitionDiskStore diskStorage) {
|
||||
|
||||
final Stream<LongList> longStream = pdbFiles.stream().flatMap(new PdbFileToLongStream(diskStorage));
|
||||
final Stream<LongList> longStream = pdbFiles.stream().flatMap(new PdbFileToLongStream(diskStorage));
|
||||
|
||||
return longStream;
|
||||
}
|
||||
return longStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PdbFile [tags=" + tags + ", rootBlockNumber=" + rootBlockNumber + ", partitionId="+partitionId+"]";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PdbFile [tags=" + tags + ", rootBlockNumber=" + rootBlockNumber + ", partitionId=" + partitionId + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (int) (rootBlockNumber ^ (rootBlockNumber >>> 32));
|
||||
result = prime * result + ((tags == null) ? 0 : tags.hashCode());
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (int) (rootBlockNumber ^ (rootBlockNumber >>> 32));
|
||||
result = prime * result + ((tags == null) ? 0 : tags.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final PdbFile other = (PdbFile) obj;
|
||||
if (rootBlockNumber != other.rootBlockNumber)
|
||||
return false;
|
||||
if (tags == null) {
|
||||
if (other.tags != null)
|
||||
return false;
|
||||
} else if (!tags.equals(other.tags))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final PdbFile other = (PdbFile) obj;
|
||||
if (rootBlockNumber != other.rootBlockNumber)
|
||||
return false;
|
||||
if (tags == null) {
|
||||
if (other.tags != null)
|
||||
return false;
|
||||
} else if (!tags.equals(other.tags))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +1,105 @@
|
||||
package org.lucares.pdb.datastore;
|
||||
|
||||
public class Proposal implements Comparable<Proposal> {
|
||||
private final String proposedTag;
|
||||
private final String proposedTag;
|
||||
|
||||
private final String proposedQuery;
|
||||
private final String proposedQuery;
|
||||
|
||||
private final boolean hasResults;
|
||||
private final boolean hasResults;
|
||||
|
||||
private final String newQuery;
|
||||
private final String newQuery;
|
||||
|
||||
private final int newCaretPosition;
|
||||
private final int newCaretPosition;
|
||||
|
||||
public Proposal(final String proposedTag, final String proposedQuery, final boolean hasResults,
|
||||
final String newQuery, final int newCaretPosition) {
|
||||
super();
|
||||
this.proposedTag = proposedTag;
|
||||
this.proposedQuery = proposedQuery;
|
||||
this.hasResults = hasResults;
|
||||
this.newQuery = newQuery;
|
||||
this.newCaretPosition = newCaretPosition;
|
||||
}
|
||||
public Proposal(final String proposedTag, final String proposedQuery, final boolean hasResults,
|
||||
final String newQuery, final int newCaretPosition) {
|
||||
super();
|
||||
this.proposedTag = proposedTag;
|
||||
this.proposedQuery = proposedQuery;
|
||||
this.hasResults = hasResults;
|
||||
this.newQuery = newQuery;
|
||||
this.newCaretPosition = newCaretPosition;
|
||||
}
|
||||
|
||||
public Proposal(final Proposal proposal, final boolean hasResults) {
|
||||
this.proposedTag = proposal.proposedTag;
|
||||
this.proposedQuery = proposal.proposedQuery;
|
||||
this.hasResults = hasResults;
|
||||
this.newQuery = proposal.newQuery;
|
||||
this.newCaretPosition = proposal.newCaretPosition;
|
||||
}
|
||||
public Proposal(final Proposal proposal, final boolean hasResults) {
|
||||
this.proposedTag = proposal.proposedTag;
|
||||
this.proposedQuery = proposal.proposedQuery;
|
||||
this.hasResults = hasResults;
|
||||
this.newQuery = proposal.newQuery;
|
||||
this.newCaretPosition = proposal.newCaretPosition;
|
||||
}
|
||||
|
||||
public String getProposedTag() {
|
||||
return proposedTag;
|
||||
}
|
||||
public String getProposedTag() {
|
||||
return proposedTag;
|
||||
}
|
||||
|
||||
public String getProposedQuery() {
|
||||
return proposedQuery;
|
||||
}
|
||||
public String getProposedQuery() {
|
||||
return proposedQuery;
|
||||
}
|
||||
|
||||
public boolean hasResults() {
|
||||
return hasResults;
|
||||
}
|
||||
public boolean hasResults() {
|
||||
return hasResults;
|
||||
}
|
||||
|
||||
public String getNewQuery() {
|
||||
return newQuery;
|
||||
}
|
||||
public String getNewQuery() {
|
||||
return newQuery;
|
||||
}
|
||||
|
||||
public int getNewCaretPosition() {
|
||||
return newCaretPosition;
|
||||
}
|
||||
public int getNewCaretPosition() {
|
||||
return newCaretPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Proposal [proposedTag=" + proposedTag + ", proposedQuery=" + proposedQuery + ", hasResults="
|
||||
+ hasResults + ", newQuery=" + newQuery + ", newCaretPosition=" + newCaretPosition + "]";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Proposal [proposedTag=" + proposedTag + ", proposedQuery=" + proposedQuery + ", hasResults="
|
||||
+ hasResults + ", newQuery=" + newQuery + ", newCaretPosition=" + newCaretPosition + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (hasResults ? 1231 : 1237);
|
||||
result = prime * result + newCaretPosition;
|
||||
result = prime * result + ((newQuery == null) ? 0 : newQuery.hashCode());
|
||||
result = prime * result + ((proposedQuery == null) ? 0 : proposedQuery.hashCode());
|
||||
result = prime * result + ((proposedTag == null) ? 0 : proposedTag.hashCode());
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (hasResults ? 1231 : 1237);
|
||||
result = prime * result + newCaretPosition;
|
||||
result = prime * result + ((newQuery == null) ? 0 : newQuery.hashCode());
|
||||
result = prime * result + ((proposedQuery == null) ? 0 : proposedQuery.hashCode());
|
||||
result = prime * result + ((proposedTag == null) ? 0 : proposedTag.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final Proposal other = (Proposal) obj;
|
||||
if (hasResults != other.hasResults)
|
||||
return false;
|
||||
if (newCaretPosition != other.newCaretPosition)
|
||||
return false;
|
||||
if (newQuery == null) {
|
||||
if (other.newQuery != null)
|
||||
return false;
|
||||
} else if (!newQuery.equals(other.newQuery))
|
||||
return false;
|
||||
if (proposedQuery == null) {
|
||||
if (other.proposedQuery != null)
|
||||
return false;
|
||||
} else if (!proposedQuery.equals(other.proposedQuery))
|
||||
return false;
|
||||
if (proposedTag == null) {
|
||||
if (other.proposedTag != null)
|
||||
return false;
|
||||
} else if (!proposedTag.equals(other.proposedTag))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final Proposal other = (Proposal) obj;
|
||||
if (hasResults != other.hasResults)
|
||||
return false;
|
||||
if (newCaretPosition != other.newCaretPosition)
|
||||
return false;
|
||||
if (newQuery == null) {
|
||||
if (other.newQuery != null)
|
||||
return false;
|
||||
} else if (!newQuery.equals(other.newQuery))
|
||||
return false;
|
||||
if (proposedQuery == null) {
|
||||
if (other.proposedQuery != null)
|
||||
return false;
|
||||
} else if (!proposedQuery.equals(other.proposedQuery))
|
||||
return false;
|
||||
if (proposedTag == null) {
|
||||
if (other.proposedTag != null)
|
||||
return false;
|
||||
} else if (!proposedTag.equals(other.proposedTag))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final Proposal o) {
|
||||
return proposedTag.compareTo(o.getProposedTag());
|
||||
}
|
||||
@Override
|
||||
public int compareTo(final Proposal o) {
|
||||
return proposedTag.compareTo(o.getProposedTag());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.lucares.pdb.datastore;
|
||||
|
||||
public class ReadException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ReadException(final RuntimeException e) {
|
||||
super(e);
|
||||
}
|
||||
public ReadException(final RuntimeException e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@ package org.lucares.pdb.datastore;
|
||||
|
||||
public class ReadRuntimeException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ReadRuntimeException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
public ReadRuntimeException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ReadRuntimeException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
public ReadRuntimeException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ReadRuntimeException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
public ReadRuntimeException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ package org.lucares.pdb.datastore;
|
||||
|
||||
public class WriteException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public WriteException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
public WriteException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public WriteException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
public WriteException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,381 +39,381 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DataStore implements AutoCloseable {
|
||||
private static final String ALL_DOCS_KEY = "\ue001allDocs"; // \ue001 is the second character in the private use
|
||||
// area
|
||||
private static final Logger EXECUTE_QUERY_LOGGER = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.dataStore.executeQuery");
|
||||
private static final Logger MAP_DOCS_TO_DOCID = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.dataStore.mapDocsToDocID");
|
||||
private final static Logger METRICS_LOGGER_NEW_WRITER = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.dataStore.newPdbWriter");
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DataStore.class);
|
||||
private static final String ALL_DOCS_KEY = "\ue001allDocs"; // \ue001 is the second character in the private use
|
||||
// area
|
||||
private static final Logger EXECUTE_QUERY_LOGGER = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.dataStore.executeQuery");
|
||||
private static final Logger MAP_DOCS_TO_DOCID = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.dataStore.mapDocsToDocID");
|
||||
private final static Logger METRICS_LOGGER_NEW_WRITER = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.dataStore.newPdbWriter");
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DataStore.class);
|
||||
|
||||
public static final char LISTING_FILE_SEPARATOR = ',';
|
||||
public static final char LISTING_FILE_SEPARATOR = ',';
|
||||
|
||||
public static final String SUBDIR_STORAGE = "storage";
|
||||
public static final String SUBDIR_STORAGE = "storage";
|
||||
|
||||
// used to generate doc ids that are
|
||||
// a) unique
|
||||
// b) monotonically increasing (this is, so that we don't have to sort the doc
|
||||
// ids when getting them from the BSFiles)
|
||||
private static final AtomicLong NEXT_DOC_ID = new AtomicLong(System.currentTimeMillis());
|
||||
// used to generate doc ids that are
|
||||
// a) unique
|
||||
// b) monotonically increasing (this is, so that we don't have to sort the doc
|
||||
// ids when getting them from the BSFiles)
|
||||
private static final AtomicLong NEXT_DOC_ID = new AtomicLong(System.currentTimeMillis());
|
||||
|
||||
public static Tag TAG_ALL_DOCS = null;
|
||||
public static Tag TAG_ALL_DOCS = null;
|
||||
|
||||
private final PartitionPersistentMap<Long, Doc, Doc> docIdToDoc;
|
||||
private final PartitionPersistentMap<Long, Doc, Doc> docIdToDoc;
|
||||
|
||||
private final PartitionPersistentMap<Tags, Long, Long> tagsToDocId;
|
||||
private final PartitionPersistentMap<Tags, Long, Long> tagsToDocId;
|
||||
|
||||
private final PartitionPersistentMap<Tag, Long, Long> tagToDocsId;
|
||||
private final PartitionPersistentMap<Tag, Long, Long> tagToDocsId;
|
||||
|
||||
private final QueryCompletionIndex queryCompletionIndex;
|
||||
private final QueryCompletionIndex queryCompletionIndex;
|
||||
|
||||
// A Doc will never be changed once it is created. Therefore we can cache them
|
||||
// easily.
|
||||
private final HotEntryCache<Long, Doc> docIdToDocCache = new HotEntryCache<>(Duration.ofMinutes(30), 100_000);
|
||||
// A Doc will never be changed once it is created. Therefore we can cache them
|
||||
// easily.
|
||||
private final HotEntryCache<Long, Doc> docIdToDocCache = new HotEntryCache<>(Duration.ofMinutes(30), 100_000);
|
||||
|
||||
private final HotEntryCache<Tags, PdbWriter> writerCache;
|
||||
private final HotEntryCache<Tags, PdbWriter> writerCache;
|
||||
|
||||
private final PartitionDiskStore diskStorage;
|
||||
private final Path storageBasePath;
|
||||
private final PartitionDiskStore diskStorage;
|
||||
private final Path storageBasePath;
|
||||
|
||||
public DataStore(final Path dataDirectory) throws IOException {
|
||||
storageBasePath = storageDirectory(dataDirectory);
|
||||
public DataStore(final Path dataDirectory) throws IOException {
|
||||
storageBasePath = storageDirectory(dataDirectory);
|
||||
|
||||
Tags.STRING_COMPRESSOR = StringCompressor.create(keyCompressionFile(storageBasePath));
|
||||
Tags.STRING_COMPRESSOR.put(ALL_DOCS_KEY);
|
||||
Tags.STRING_COMPRESSOR.put("");
|
||||
TAG_ALL_DOCS = new Tag(ALL_DOCS_KEY, ""); // Tag(String, String) uses the StringCompressor internally, so it
|
||||
// must be initialized after the string compressor has been created
|
||||
Tags.STRING_COMPRESSOR = StringCompressor.create(keyCompressionFile(storageBasePath));
|
||||
Tags.STRING_COMPRESSOR.put(ALL_DOCS_KEY);
|
||||
Tags.STRING_COMPRESSOR.put("");
|
||||
TAG_ALL_DOCS = new Tag(ALL_DOCS_KEY, ""); // Tag(String, String) uses the StringCompressor internally, so it
|
||||
// must be initialized after the string compressor has been created
|
||||
|
||||
diskStorage = new PartitionDiskStore(storageBasePath, "data.bs");
|
||||
diskStorage = new PartitionDiskStore(storageBasePath, "data.bs");
|
||||
|
||||
tagToDocsId = new PartitionPersistentMap<>(storageBasePath, "keyToValueToDocIdsIndex.bs",
|
||||
new TagEncoderDecoder(), PartitionAwareWrapper.wrap(PersistentMap.LONG_CODER));
|
||||
tagToDocsId = new PartitionPersistentMap<>(storageBasePath, "keyToValueToDocIdsIndex.bs",
|
||||
new TagEncoderDecoder(), PartitionAwareWrapper.wrap(PersistentMap.LONG_CODER));
|
||||
|
||||
tagsToDocId = new PartitionPersistentMap<>(storageBasePath, "tagsToDocIdIndex.bs", new TagsEncoderDecoder(),
|
||||
PartitionAwareWrapper.wrap(PersistentMap.LONG_CODER));
|
||||
tagsToDocId = new PartitionPersistentMap<>(storageBasePath, "tagsToDocIdIndex.bs", new TagsEncoderDecoder(),
|
||||
PartitionAwareWrapper.wrap(PersistentMap.LONG_CODER));
|
||||
|
||||
docIdToDoc = new PartitionPersistentMap<>(storageBasePath, "docIdToDocIndex.bs", PersistentMap.LONG_CODER,
|
||||
new DocEncoderDecoder());
|
||||
docIdToDoc = new PartitionPersistentMap<>(storageBasePath, "docIdToDocIndex.bs", PersistentMap.LONG_CODER,
|
||||
new DocEncoderDecoder());
|
||||
|
||||
queryCompletionIndex = new QueryCompletionIndex(storageBasePath);
|
||||
queryCompletionIndex = new QueryCompletionIndex(storageBasePath);
|
||||
|
||||
writerCache = new HotEntryCache<>(Duration.ofSeconds(10), 1000);
|
||||
writerCache.addListener((key, value) -> value.close());
|
||||
}
|
||||
writerCache = new HotEntryCache<>(Duration.ofSeconds(10), 1000);
|
||||
writerCache.addListener((key, value) -> value.close());
|
||||
}
|
||||
|
||||
private Path keyCompressionFile(final Path dataDirectory) throws IOException {
|
||||
return dataDirectory.resolve("keys.csv");
|
||||
}
|
||||
private Path keyCompressionFile(final Path dataDirectory) throws IOException {
|
||||
return dataDirectory.resolve("keys.csv");
|
||||
}
|
||||
|
||||
public static Path storageDirectory(final Path dataDirectory) throws IOException {
|
||||
return dataDirectory.resolve(SUBDIR_STORAGE);
|
||||
}
|
||||
public static Path storageDirectory(final Path dataDirectory) throws IOException {
|
||||
return dataDirectory.resolve(SUBDIR_STORAGE);
|
||||
}
|
||||
|
||||
public void write(final long dateAsEpochMilli, final Tags tags, final long value) {
|
||||
final ParititionId partitionId = DateIndexExtension.toPartitionId(dateAsEpochMilli);
|
||||
final PdbWriter writer = getWriter(partitionId, tags);
|
||||
writer.write(dateAsEpochMilli, value);
|
||||
}
|
||||
public void write(final long dateAsEpochMilli, final Tags tags, final long value) {
|
||||
final ParititionId partitionId = DateIndexExtension.toPartitionId(dateAsEpochMilli);
|
||||
final PdbWriter writer = getWriter(partitionId, tags);
|
||||
writer.write(dateAsEpochMilli, value);
|
||||
}
|
||||
|
||||
// visible for test
|
||||
QueryCompletionIndex getQueryCompletionIndex() {
|
||||
return queryCompletionIndex;
|
||||
}
|
||||
// visible for test
|
||||
QueryCompletionIndex getQueryCompletionIndex() {
|
||||
return queryCompletionIndex;
|
||||
}
|
||||
|
||||
public long createNewFile(final ParititionId partitionId, final Tags tags) {
|
||||
try {
|
||||
final long newFilesRootBlockOffset = diskStorage.allocateBlock(partitionId, BSFile.BLOCK_SIZE);
|
||||
public long createNewFile(final ParititionId partitionId, final Tags tags) {
|
||||
try {
|
||||
final long newFilesRootBlockOffset = diskStorage.allocateBlock(partitionId, BSFile.BLOCK_SIZE);
|
||||
|
||||
final long docId = createUniqueDocId();
|
||||
final Doc doc = new Doc(partitionId, tags, newFilesRootBlockOffset);
|
||||
docIdToDoc.putValue(partitionId, docId, doc);
|
||||
final long docId = createUniqueDocId();
|
||||
final Doc doc = new Doc(partitionId, tags, newFilesRootBlockOffset);
|
||||
docIdToDoc.putValue(partitionId, docId, doc);
|
||||
|
||||
final Long oldDocId = tagsToDocId.putValue(partitionId, tags, docId);
|
||||
Preconditions.checkNull(oldDocId, "There must be at most one document for tags: {0}", tags);
|
||||
final Long oldDocId = tagsToDocId.putValue(partitionId, tags, docId);
|
||||
Preconditions.checkNull(oldDocId, "There must be at most one document for tags: {0}", tags);
|
||||
|
||||
// store mapping from tag to docId, so that we can find all docs for a given tag
|
||||
final List<Tag> ts = new ArrayList<>(tags.toTags());
|
||||
ts.add(TAG_ALL_DOCS);
|
||||
for (final Tag tag : ts) {
|
||||
// store mapping from tag to docId, so that we can find all docs for a given tag
|
||||
final List<Tag> ts = new ArrayList<>(tags.toTags());
|
||||
ts.add(TAG_ALL_DOCS);
|
||||
for (final Tag tag : ts) {
|
||||
|
||||
Long diskStoreOffsetForDocIdsOfTag = tagToDocsId.getValue(partitionId, tag);
|
||||
Long diskStoreOffsetForDocIdsOfTag = tagToDocsId.getValue(partitionId, tag);
|
||||
|
||||
if (diskStoreOffsetForDocIdsOfTag == null) {
|
||||
diskStoreOffsetForDocIdsOfTag = diskStorage.allocateBlock(partitionId, BSFile.BLOCK_SIZE);
|
||||
tagToDocsId.putValue(partitionId, tag, diskStoreOffsetForDocIdsOfTag);
|
||||
}
|
||||
if (diskStoreOffsetForDocIdsOfTag == null) {
|
||||
diskStoreOffsetForDocIdsOfTag = diskStorage.allocateBlock(partitionId, BSFile.BLOCK_SIZE);
|
||||
tagToDocsId.putValue(partitionId, tag, diskStoreOffsetForDocIdsOfTag);
|
||||
}
|
||||
|
||||
try (final LongStreamFile docIdsOfTag = diskStorage.streamExistingFile(diskStoreOffsetForDocIdsOfTag,
|
||||
partitionId)) {
|
||||
docIdsOfTag.append(docId);
|
||||
}
|
||||
}
|
||||
try (final LongStreamFile docIdsOfTag = diskStorage.streamExistingFile(diskStoreOffsetForDocIdsOfTag,
|
||||
partitionId)) {
|
||||
docIdsOfTag.append(docId);
|
||||
}
|
||||
}
|
||||
|
||||
// index the tags, so that we can efficiently find all possible values for a
|
||||
// field in a query
|
||||
queryCompletionIndex.addTags(partitionId, tags);
|
||||
// index the tags, so that we can efficiently find all possible values for a
|
||||
// field in a query
|
||||
queryCompletionIndex.addTags(partitionId, tags);
|
||||
|
||||
return newFilesRootBlockOffset;
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
return newFilesRootBlockOffset;
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private long createUniqueDocId() {
|
||||
return NEXT_DOC_ID.getAndIncrement();
|
||||
}
|
||||
private long createUniqueDocId() {
|
||||
return NEXT_DOC_ID.getAndIncrement();
|
||||
}
|
||||
|
||||
public List<PdbFile> getFilesForQuery(final Query query) {
|
||||
public List<PdbFile> getFilesForQuery(final Query query) {
|
||||
|
||||
final List<Doc> searchResult = search(query);
|
||||
if (searchResult.size() > 500_000) {
|
||||
throw new IllegalStateException("Too many results.");
|
||||
}
|
||||
final List<Doc> searchResult = search(query);
|
||||
if (searchResult.size() > 500_000) {
|
||||
throw new IllegalStateException("Too many results.");
|
||||
}
|
||||
|
||||
final List<PdbFile> result = toPdbFiles(searchResult);
|
||||
return result;
|
||||
}
|
||||
final List<PdbFile> result = toPdbFiles(searchResult);
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<PdbFile> toPdbFiles(final List<Doc> searchResult) {
|
||||
final List<PdbFile> result = new ArrayList<>(searchResult.size());
|
||||
for (final Doc document : searchResult) {
|
||||
private List<PdbFile> toPdbFiles(final List<Doc> searchResult) {
|
||||
final List<PdbFile> result = new ArrayList<>(searchResult.size());
|
||||
for (final Doc document : searchResult) {
|
||||
|
||||
final ParititionId partitionId = document.getPartitionId();
|
||||
final long rootBlockNumber = document.getRootBlockNumber();
|
||||
final Tags tags = document.getTags();
|
||||
final PdbFile pdbFile = new PdbFile(partitionId, rootBlockNumber, tags);
|
||||
final ParititionId partitionId = document.getPartitionId();
|
||||
final long rootBlockNumber = document.getRootBlockNumber();
|
||||
final Tags tags = document.getTags();
|
||||
final PdbFile pdbFile = new PdbFile(partitionId, rootBlockNumber, tags);
|
||||
|
||||
result.add(pdbFile);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
result.add(pdbFile);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Doc> search(final Query query) {
|
||||
try {
|
||||
final List<Doc> result = new ArrayList<>();
|
||||
public List<Doc> search(final Query query) {
|
||||
try {
|
||||
final List<Doc> result = new ArrayList<>();
|
||||
|
||||
final PartitionLongList docIdsList = executeQuery(query);
|
||||
LOGGER.trace("query {} found {} docs", query, docIdsList.size());
|
||||
final List<Doc> docs = mapDocIdsToDocs(docIdsList);
|
||||
result.addAll(docs);
|
||||
|
||||
return result;
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int count(final Query query) {
|
||||
final PartitionLongList docIdsList = executeQuery(query);
|
||||
return docIdsList.size();
|
||||
}
|
||||
|
||||
public List<String> getAvailableFields(final DateTimeRange dateRange) {
|
||||
|
||||
final Set<String> keys = new HashSet<>();
|
||||
|
||||
final Tag keyPrefix = new Tag("", ""); // will find everything
|
||||
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
tagToDocsId.visitValues(partitionIdSource, keyPrefix, (tags, __) -> keys.add(tags.getKeyAsString()));
|
||||
|
||||
keys.remove(ALL_DOCS_KEY);
|
||||
final List<String> result = new ArrayList<>(keys);
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private PartitionLongList executeQuery(final Query query) {
|
||||
final long start = System.nanoTime();
|
||||
synchronized (docIdToDoc) {
|
||||
final Expression expression = QueryLanguageParser.parse(query.getQuery());
|
||||
final ExpressionToDocIdVisitor visitor = new ExpressionToDocIdVisitor(query.getDateRange(), tagToDocsId,
|
||||
diskStorage);
|
||||
final PartitionLongList docIdsList = expression.visit(visitor);
|
||||
EXECUTE_QUERY_LOGGER.debug("executeQuery({}) took {}ms returned {} results ", query,
|
||||
(System.nanoTime() - start) / 1_000_000.0, docIdsList.size());
|
||||
return docIdsList;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Doc> mapDocIdsToDocs(final PartitionLongList docIdsList) throws IOException {
|
||||
final List<Doc> result = new ArrayList<>(docIdsList.size());
|
||||
|
||||
synchronized (docIdToDoc) {
|
||||
final long start = System.nanoTime();
|
||||
|
||||
for (final ParititionId partitionId : docIdsList) {
|
||||
final LongList docIds = docIdsList.get(partitionId);
|
||||
|
||||
for (int i = 0; i < docIds.size(); i++) {
|
||||
final long docId = docIds.get(i);
|
||||
|
||||
final Doc doc = getDocByDocId(partitionId, docId);
|
||||
Objects.requireNonNull(doc, "Doc with id " + docId + " did not exist.");
|
||||
|
||||
result.add(doc);
|
||||
}
|
||||
}
|
||||
|
||||
MAP_DOCS_TO_DOCID.debug("mapDocIdsToDocs({}): {}ms", docIdsList.size(),
|
||||
(System.nanoTime() - start) / 1_000_000.0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Optional<Doc> getByTags(final ParititionId partitionId, final Tags tags) {
|
||||
|
||||
final Long docId = tagsToDocId.getValue(partitionId, tags);
|
||||
|
||||
if (docId != null) {
|
||||
final Doc doc = getDocByDocId(partitionId, docId);
|
||||
return Optional.of(doc);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public List<Doc> getByTags(final DateTimeRange dateRange, final Tags tags) {
|
||||
|
||||
final List<Doc> result = new ArrayList<>();
|
||||
final DatePartitioner datePartitioner = new DatePartitioner(dateRange);
|
||||
final List<Long> docIds = tagsToDocId.getValues(datePartitioner, tags);
|
||||
for (final Long docId : docIds) {
|
||||
|
||||
if (docId != null) {
|
||||
final Doc doc = getDocByDocId(dateRange, docId);
|
||||
result.add(doc);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Doc getDocByDocId(final ParititionId partitionId, final Long docId) {
|
||||
return docIdToDocCache.putIfAbsent(docId, documentId -> {
|
||||
return docIdToDoc.getValue(partitionId, documentId);
|
||||
});
|
||||
}
|
||||
|
||||
private Doc getDocByDocId(final DateTimeRange dateRange, final Long docId) {
|
||||
return docIdToDocCache.putIfAbsent(docId, documentId -> {
|
||||
|
||||
final DatePartitioner datePartitioner = new DatePartitioner(dateRange);
|
||||
final List<Doc> docIds = docIdToDoc.getValues(datePartitioner, documentId);
|
||||
if (docIds.size() == 1) {
|
||||
return docIds.get(0);
|
||||
} else if (docIds.size() > 1) {
|
||||
throw new IllegalStateException(
|
||||
"Found multiple documents for " + dateRange + " and docId " + documentId + ": " + docIds);
|
||||
}
|
||||
throw new IllegalStateException("Found no documents for " + dateRange + " and docId " + documentId);
|
||||
});
|
||||
}
|
||||
|
||||
public List<Proposal> propose(final QueryWithCaretMarker query) {
|
||||
|
||||
final NewProposerParser newProposerParser = new NewProposerParser(queryCompletionIndex);
|
||||
final List<Proposal> proposals = newProposerParser.propose(query);
|
||||
LOGGER.debug("Proposals for query {}: {}", query, proposals);
|
||||
return proposals;
|
||||
}
|
||||
|
||||
public PartitionDiskStore getDiskStorage() {
|
||||
return diskStorage;
|
||||
}
|
||||
|
||||
private PdbWriter getWriter(final ParititionId partitionId, final Tags tags) throws ReadException, WriteException {
|
||||
|
||||
return writerCache.putIfAbsent(tags, t -> getWriterInternal(partitionId, tags));
|
||||
}
|
||||
|
||||
// visible for test
|
||||
long sizeWriterCache() {
|
||||
return writerCache.size();
|
||||
}
|
||||
|
||||
private PdbWriter getWriterInternal(final ParititionId partitionId, final Tags tags) {
|
||||
final Optional<Doc> docsForTags = getByTags(partitionId, tags);
|
||||
PdbWriter writer;
|
||||
if (docsForTags.isPresent()) {
|
||||
try {
|
||||
final Doc doc = docsForTags.get();
|
||||
final PdbFile pdbFile = new PdbFile(partitionId, doc.getRootBlockNumber(), tags);
|
||||
writer = new PdbWriter(pdbFile, diskStorage.getExisting(partitionId));
|
||||
} catch (final RuntimeException e) {
|
||||
throw new ReadException(e);
|
||||
}
|
||||
} else {
|
||||
writer = newPdbWriter(partitionId, tags);
|
||||
}
|
||||
return writer;
|
||||
}
|
||||
|
||||
private PdbWriter newPdbWriter(final ParititionId partitionId, final Tags tags) {
|
||||
final long start = System.nanoTime();
|
||||
try {
|
||||
final PdbFile pdbFile = createNewPdbFile(partitionId, tags);
|
||||
final PdbWriter result = new PdbWriter(pdbFile, diskStorage.getExisting(partitionId));
|
||||
|
||||
METRICS_LOGGER_NEW_WRITER.debug("newPdbWriter took {}ms tags: {}",
|
||||
(System.nanoTime() - start) / 1_000_000.0, tags);
|
||||
return result;
|
||||
} catch (final RuntimeException e) {
|
||||
throw new WriteException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PdbFile createNewPdbFile(final ParititionId partitionId, final Tags tags) {
|
||||
|
||||
final long rootBlockNumber = createNewFile(partitionId, tags);
|
||||
|
||||
final PdbFile result = new PdbFile(partitionId, rootBlockNumber, tags);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws RuntimeIOException {
|
||||
try {
|
||||
// we cannot simply clear the cache, because the cache implementation (Guava at
|
||||
// the time of writing) handles eviction events asynchronously.
|
||||
forEachWriter(cachedWriter -> {
|
||||
try {
|
||||
cachedWriter.close();
|
||||
} catch (final Exception e) {
|
||||
throw new WriteException(e);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
try {
|
||||
diskStorage.close();
|
||||
} finally {
|
||||
tagToDocsId.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void forEachWriter(final Consumer<PdbWriter> consumer) {
|
||||
writerCache.forEach(writer -> {
|
||||
try {
|
||||
consumer.accept(writer);
|
||||
} catch (final RuntimeException e) {
|
||||
LOGGER.warn("Exception while applying consumer to PdbWriter for " + writer.getPdbFile(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
forEachWriter(t -> {
|
||||
try {
|
||||
t.flush();
|
||||
} catch (final Exception e) {
|
||||
throw new WriteException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
final PartitionLongList docIdsList = executeQuery(query);
|
||||
LOGGER.trace("query {} found {} docs", query, docIdsList.size());
|
||||
final List<Doc> docs = mapDocIdsToDocs(docIdsList);
|
||||
result.addAll(docs);
|
||||
|
||||
return result;
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int count(final Query query) {
|
||||
final PartitionLongList docIdsList = executeQuery(query);
|
||||
return docIdsList.size();
|
||||
}
|
||||
|
||||
public List<String> getAvailableFields(final DateTimeRange dateRange) {
|
||||
|
||||
final Set<String> keys = new HashSet<>();
|
||||
|
||||
final Tag keyPrefix = new Tag("", ""); // will find everything
|
||||
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
tagToDocsId.visitValues(partitionIdSource, keyPrefix, (tags, __) -> keys.add(tags.getKeyAsString()));
|
||||
|
||||
keys.remove(ALL_DOCS_KEY);
|
||||
final List<String> result = new ArrayList<>(keys);
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private PartitionLongList executeQuery(final Query query) {
|
||||
final long start = System.nanoTime();
|
||||
synchronized (docIdToDoc) {
|
||||
final Expression expression = QueryLanguageParser.parse(query.getQuery());
|
||||
final ExpressionToDocIdVisitor visitor = new ExpressionToDocIdVisitor(query.getDateRange(), tagToDocsId,
|
||||
diskStorage);
|
||||
final PartitionLongList docIdsList = expression.visit(visitor);
|
||||
EXECUTE_QUERY_LOGGER.debug("executeQuery({}) took {}ms returned {} results ", query,
|
||||
(System.nanoTime() - start) / 1_000_000.0, docIdsList.size());
|
||||
return docIdsList;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Doc> mapDocIdsToDocs(final PartitionLongList docIdsList) throws IOException {
|
||||
final List<Doc> result = new ArrayList<>(docIdsList.size());
|
||||
|
||||
synchronized (docIdToDoc) {
|
||||
final long start = System.nanoTime();
|
||||
|
||||
for (final ParititionId partitionId : docIdsList) {
|
||||
final LongList docIds = docIdsList.get(partitionId);
|
||||
|
||||
for (int i = 0; i < docIds.size(); i++) {
|
||||
final long docId = docIds.get(i);
|
||||
|
||||
final Doc doc = getDocByDocId(partitionId, docId);
|
||||
Objects.requireNonNull(doc, "Doc with id " + docId + " did not exist.");
|
||||
|
||||
result.add(doc);
|
||||
}
|
||||
}
|
||||
|
||||
MAP_DOCS_TO_DOCID.debug("mapDocIdsToDocs({}): {}ms", docIdsList.size(),
|
||||
(System.nanoTime() - start) / 1_000_000.0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Optional<Doc> getByTags(final ParititionId partitionId, final Tags tags) {
|
||||
|
||||
final Long docId = tagsToDocId.getValue(partitionId, tags);
|
||||
|
||||
if (docId != null) {
|
||||
final Doc doc = getDocByDocId(partitionId, docId);
|
||||
return Optional.of(doc);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public List<Doc> getByTags(final DateTimeRange dateRange, final Tags tags) {
|
||||
|
||||
final List<Doc> result = new ArrayList<>();
|
||||
final DatePartitioner datePartitioner = new DatePartitioner(dateRange);
|
||||
final List<Long> docIds = tagsToDocId.getValues(datePartitioner, tags);
|
||||
for (final Long docId : docIds) {
|
||||
|
||||
if (docId != null) {
|
||||
final Doc doc = getDocByDocId(dateRange, docId);
|
||||
result.add(doc);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Doc getDocByDocId(final ParititionId partitionId, final Long docId) {
|
||||
return docIdToDocCache.putIfAbsent(docId, documentId -> {
|
||||
return docIdToDoc.getValue(partitionId, documentId);
|
||||
});
|
||||
}
|
||||
|
||||
private Doc getDocByDocId(final DateTimeRange dateRange, final Long docId) {
|
||||
return docIdToDocCache.putIfAbsent(docId, documentId -> {
|
||||
|
||||
final DatePartitioner datePartitioner = new DatePartitioner(dateRange);
|
||||
final List<Doc> docIds = docIdToDoc.getValues(datePartitioner, documentId);
|
||||
if (docIds.size() == 1) {
|
||||
return docIds.get(0);
|
||||
} else if (docIds.size() > 1) {
|
||||
throw new IllegalStateException(
|
||||
"Found multiple documents for " + dateRange + " and docId " + documentId + ": " + docIds);
|
||||
}
|
||||
throw new IllegalStateException("Found no documents for " + dateRange + " and docId " + documentId);
|
||||
});
|
||||
}
|
||||
|
||||
public List<Proposal> propose(final QueryWithCaretMarker query) {
|
||||
|
||||
final NewProposerParser newProposerParser = new NewProposerParser(queryCompletionIndex);
|
||||
final List<Proposal> proposals = newProposerParser.propose(query);
|
||||
LOGGER.debug("Proposals for query {}: {}", query, proposals);
|
||||
return proposals;
|
||||
}
|
||||
|
||||
public PartitionDiskStore getDiskStorage() {
|
||||
return diskStorage;
|
||||
}
|
||||
|
||||
private PdbWriter getWriter(final ParititionId partitionId, final Tags tags) throws ReadException, WriteException {
|
||||
|
||||
return writerCache.putIfAbsent(tags, t -> getWriterInternal(partitionId, tags));
|
||||
}
|
||||
|
||||
// visible for test
|
||||
long sizeWriterCache() {
|
||||
return writerCache.size();
|
||||
}
|
||||
|
||||
private PdbWriter getWriterInternal(final ParititionId partitionId, final Tags tags) {
|
||||
final Optional<Doc> docsForTags = getByTags(partitionId, tags);
|
||||
PdbWriter writer;
|
||||
if (docsForTags.isPresent()) {
|
||||
try {
|
||||
final Doc doc = docsForTags.get();
|
||||
final PdbFile pdbFile = new PdbFile(partitionId, doc.getRootBlockNumber(), tags);
|
||||
writer = new PdbWriter(pdbFile, diskStorage.getExisting(partitionId));
|
||||
} catch (final RuntimeException e) {
|
||||
throw new ReadException(e);
|
||||
}
|
||||
} else {
|
||||
writer = newPdbWriter(partitionId, tags);
|
||||
}
|
||||
return writer;
|
||||
}
|
||||
|
||||
private PdbWriter newPdbWriter(final ParititionId partitionId, final Tags tags) {
|
||||
final long start = System.nanoTime();
|
||||
try {
|
||||
final PdbFile pdbFile = createNewPdbFile(partitionId, tags);
|
||||
final PdbWriter result = new PdbWriter(pdbFile, diskStorage.getExisting(partitionId));
|
||||
|
||||
METRICS_LOGGER_NEW_WRITER.debug("newPdbWriter took {}ms tags: {}",
|
||||
(System.nanoTime() - start) / 1_000_000.0, tags);
|
||||
return result;
|
||||
} catch (final RuntimeException e) {
|
||||
throw new WriteException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PdbFile createNewPdbFile(final ParititionId partitionId, final Tags tags) {
|
||||
|
||||
final long rootBlockNumber = createNewFile(partitionId, tags);
|
||||
|
||||
final PdbFile result = new PdbFile(partitionId, rootBlockNumber, tags);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws RuntimeIOException {
|
||||
try {
|
||||
// we cannot simply clear the cache, because the cache implementation (Guava at
|
||||
// the time of writing) handles eviction events asynchronously.
|
||||
forEachWriter(cachedWriter -> {
|
||||
try {
|
||||
cachedWriter.close();
|
||||
} catch (final Exception e) {
|
||||
throw new WriteException(e);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
try {
|
||||
diskStorage.close();
|
||||
} finally {
|
||||
tagToDocsId.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void forEachWriter(final Consumer<PdbWriter> consumer) {
|
||||
writerCache.forEach(writer -> {
|
||||
try {
|
||||
consumer.accept(writer);
|
||||
} catch (final RuntimeException e) {
|
||||
LOGGER.warn("Exception while applying consumer to PdbWriter for " + writer.getPdbFile(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
forEachWriter(t -> {
|
||||
try {
|
||||
t.flush();
|
||||
} catch (final Exception e) {
|
||||
throw new WriteException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,178 +19,178 @@ import org.lucares.pdb.api.DateTimeRange;
|
||||
|
||||
public class DateIndexExtension {
|
||||
|
||||
/**
|
||||
* This date pattern defines the resolution of the date index
|
||||
*/
|
||||
private static final DateTimeFormatter DATE_PATTERN = DateTimeFormatter.ofPattern("yyyyMM");
|
||||
/**
|
||||
* This date pattern defines the resolution of the date index
|
||||
*/
|
||||
private static final DateTimeFormatter DATE_PATTERN = DateTimeFormatter.ofPattern("yyyyMM");
|
||||
|
||||
// visible for test
|
||||
static final ConcurrentNavigableMap<Long, DatePrefixAndRange> DATE_PREFIX_CACHE = new ConcurrentSkipListMap<>();
|
||||
// visible for test
|
||||
static final ConcurrentNavigableMap<Long, DatePrefixAndRange> DATE_PREFIX_CACHE = new ConcurrentSkipListMap<>();
|
||||
|
||||
private static final AtomicReference<DatePrefixAndRange> LAST_ACCESSED = new AtomicReference<>(null);
|
||||
private static final AtomicReference<DatePrefixAndRange> LAST_ACCESSED = new AtomicReference<>(null);
|
||||
|
||||
static Set<String> toDateIndexPrefix(final DateTimeRange dateRange) {
|
||||
final Set<String> result = new TreeSet<>();
|
||||
static Set<String> toDateIndexPrefix(final DateTimeRange dateRange) {
|
||||
final Set<String> result = new TreeSet<>();
|
||||
|
||||
OffsetDateTime current = dateRange.getStart();
|
||||
while (current.isBefore(dateRange.getEnd())) {
|
||||
OffsetDateTime current = dateRange.getStart();
|
||||
while (current.isBefore(dateRange.getEnd())) {
|
||||
|
||||
result.add(toDateIndexPrefix(current));
|
||||
current = current.plusMonths(1);
|
||||
result.add(toDateIndexPrefix(current));
|
||||
current = current.plusMonths(1);
|
||||
|
||||
}
|
||||
result.add(toDateIndexPrefix(dateRange.getEnd()));
|
||||
}
|
||||
result.add(toDateIndexPrefix(dateRange.getEnd()));
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static String toDateIndexPrefix(final OffsetDateTime time) {
|
||||
return time.format(DATE_PATTERN);
|
||||
}
|
||||
static String toDateIndexPrefix(final OffsetDateTime time) {
|
||||
return time.format(DATE_PATTERN);
|
||||
}
|
||||
|
||||
public static ParititionId toPartitionId(final long epochMilli) {
|
||||
String result;
|
||||
final DatePrefixAndRange lastAccessed = LAST_ACCESSED.get();
|
||||
if (lastAccessed != null && lastAccessed.getMinEpochMilli() <= epochMilli
|
||||
&& lastAccessed.getMaxEpochMilli() >= epochMilli) {
|
||||
result = lastAccessed.getDatePrefix();
|
||||
} else {
|
||||
final Entry<Long, DatePrefixAndRange> value = DATE_PREFIX_CACHE.floorEntry(epochMilli);
|
||||
public static ParititionId toPartitionId(final long epochMilli) {
|
||||
String result;
|
||||
final DatePrefixAndRange lastAccessed = LAST_ACCESSED.get();
|
||||
if (lastAccessed != null && lastAccessed.getMinEpochMilli() <= epochMilli
|
||||
&& lastAccessed.getMaxEpochMilli() >= epochMilli) {
|
||||
result = lastAccessed.getDatePrefix();
|
||||
} else {
|
||||
final Entry<Long, DatePrefixAndRange> value = DATE_PREFIX_CACHE.floorEntry(epochMilli);
|
||||
|
||||
if (value == null || !value.getValue().contains(epochMilli)) {
|
||||
final DatePrefixAndRange newValue = toDatePrefixAndRange(epochMilli);
|
||||
DATE_PREFIX_CACHE.put(newValue.getMinEpochMilli(), newValue);
|
||||
result = newValue.getDatePrefix();
|
||||
LAST_ACCESSED.set(newValue);
|
||||
} else {
|
||||
result = value.getValue().getDatePrefix();
|
||||
LAST_ACCESSED.set(value.getValue());
|
||||
}
|
||||
}
|
||||
return new ParititionId(result);
|
||||
}
|
||||
if (value == null || !value.getValue().contains(epochMilli)) {
|
||||
final DatePrefixAndRange newValue = toDatePrefixAndRange(epochMilli);
|
||||
DATE_PREFIX_CACHE.put(newValue.getMinEpochMilli(), newValue);
|
||||
result = newValue.getDatePrefix();
|
||||
LAST_ACCESSED.set(newValue);
|
||||
} else {
|
||||
result = value.getValue().getDatePrefix();
|
||||
LAST_ACCESSED.set(value.getValue());
|
||||
}
|
||||
}
|
||||
return new ParititionId(result);
|
||||
}
|
||||
|
||||
public static String toDateIndexPrefix(final long epochMilli) {
|
||||
public static String toDateIndexPrefix(final long epochMilli) {
|
||||
|
||||
final Entry<Long, DatePrefixAndRange> value = DATE_PREFIX_CACHE.floorEntry(epochMilli);
|
||||
final Entry<Long, DatePrefixAndRange> value = DATE_PREFIX_CACHE.floorEntry(epochMilli);
|
||||
|
||||
String result;
|
||||
if (value == null || !value.getValue().contains(epochMilli)) {
|
||||
final DatePrefixAndRange newValue = toDatePrefixAndRange(epochMilli);
|
||||
DATE_PREFIX_CACHE.put(newValue.getMinEpochMilli(), newValue);
|
||||
result = newValue.getDatePrefix();
|
||||
} else {
|
||||
result = value.getValue().getDatePrefix();
|
||||
}
|
||||
String result;
|
||||
if (value == null || !value.getValue().contains(epochMilli)) {
|
||||
final DatePrefixAndRange newValue = toDatePrefixAndRange(epochMilli);
|
||||
DATE_PREFIX_CACHE.put(newValue.getMinEpochMilli(), newValue);
|
||||
result = newValue.getDatePrefix();
|
||||
} else {
|
||||
result = value.getValue().getDatePrefix();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* only for tests, use toPartitionIds(final DateTimeRange dateRange,final
|
||||
* Collection<? extends PartitionId> availablePartitionIds) instead
|
||||
*
|
||||
* @param dateRange
|
||||
* @return
|
||||
*/
|
||||
static List<ParititionId> toPartitionIds(final DateTimeRange dateRange) {
|
||||
final List<ParititionId> result = new ArrayList<>();
|
||||
/**
|
||||
* only for tests, use toPartitionIds(final DateTimeRange dateRange,final
|
||||
* Collection<? extends PartitionId> availablePartitionIds) instead
|
||||
*
|
||||
* @param dateRange
|
||||
* @return
|
||||
*/
|
||||
static List<ParititionId> toPartitionIds(final DateTimeRange dateRange) {
|
||||
final List<ParititionId> result = new ArrayList<>();
|
||||
|
||||
OffsetDateTime current = dateRange.getStart();
|
||||
final OffsetDateTime end = dateRange.getEnd();
|
||||
current = current.withOffsetSameInstant(ZoneOffset.UTC).withDayOfMonth(1).withHour(0).withMinute(0)
|
||||
.withSecond(0).withNano(0);
|
||||
OffsetDateTime current = dateRange.getStart();
|
||||
final OffsetDateTime end = dateRange.getEnd();
|
||||
current = current.withOffsetSameInstant(ZoneOffset.UTC).withDayOfMonth(1).withHour(0).withMinute(0)
|
||||
.withSecond(0).withNano(0);
|
||||
|
||||
while (!current.isAfter(end)) {
|
||||
final String id = current.format(DATE_PATTERN);
|
||||
final ParititionId partitionId = new ParititionId(id);
|
||||
result.add(partitionId);
|
||||
current = current.plusMonths(1);
|
||||
}
|
||||
while (!current.isAfter(end)) {
|
||||
final String id = current.format(DATE_PATTERN);
|
||||
final ParititionId partitionId = new ParititionId(id);
|
||||
result.add(partitionId);
|
||||
current = current.plusMonths(1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Set<ParititionId> toPartitionIds(final DateTimeRange dateRange,
|
||||
final Collection<? extends ParititionId> availablePartitionIds) {
|
||||
final Set<ParititionId> result = new LinkedHashSet<>();
|
||||
public static Set<ParititionId> toPartitionIds(final DateTimeRange dateRange,
|
||||
final Collection<? extends ParititionId> availablePartitionIds) {
|
||||
final Set<ParititionId> result = new LinkedHashSet<>();
|
||||
|
||||
final ParititionId start = toPartitionId(dateRange.getStart().toInstant().toEpochMilli());
|
||||
final ParititionId end = toPartitionId(dateRange.getEnd().toInstant().toEpochMilli());
|
||||
final ParititionId start = toPartitionId(dateRange.getStart().toInstant().toEpochMilli());
|
||||
final ParititionId end = toPartitionId(dateRange.getEnd().toInstant().toEpochMilli());
|
||||
|
||||
for (final ParititionId partitionId : availablePartitionIds) {
|
||||
if (start.compareTo(partitionId) <= 0 && end.compareTo(partitionId) >= 0) {
|
||||
result.add(partitionId);
|
||||
}
|
||||
}
|
||||
for (final ParititionId partitionId : availablePartitionIds) {
|
||||
if (start.compareTo(partitionId) <= 0 && end.compareTo(partitionId) >= 0) {
|
||||
result.add(partitionId);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static DatePrefixAndRange toDatePrefixAndRange(final long epochMilli) {
|
||||
final OffsetDateTime date = Instant.ofEpochMilli(epochMilli).atOffset(ZoneOffset.UTC);
|
||||
final OffsetDateTime beginOfMonth = date.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
final OffsetDateTime endOfMonth = beginOfMonth.plusMonths(1).minusNanos(1);
|
||||
public static DatePrefixAndRange toDatePrefixAndRange(final long epochMilli) {
|
||||
final OffsetDateTime date = Instant.ofEpochMilli(epochMilli).atOffset(ZoneOffset.UTC);
|
||||
final OffsetDateTime beginOfMonth = date.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
final OffsetDateTime endOfMonth = beginOfMonth.plusMonths(1).minusNanos(1);
|
||||
|
||||
final String datePrefix = date.format(DATE_PATTERN);
|
||||
final long minEpochMilli = beginOfMonth.toInstant().toEpochMilli();
|
||||
final long maxEpochMilli = endOfMonth.toInstant().toEpochMilli();
|
||||
final String datePrefix = date.format(DATE_PATTERN);
|
||||
final long minEpochMilli = beginOfMonth.toInstant().toEpochMilli();
|
||||
final long maxEpochMilli = endOfMonth.toInstant().toEpochMilli();
|
||||
|
||||
return new DatePrefixAndRange(datePrefix, minEpochMilli, maxEpochMilli);
|
||||
}
|
||||
return new DatePrefixAndRange(datePrefix, minEpochMilli, maxEpochMilli);
|
||||
}
|
||||
|
||||
public static List<Long> toDateIndexEpochMillis(final DateTimeRange dateRange) {
|
||||
final List<Long> result = new ArrayList<>();
|
||||
public static List<Long> toDateIndexEpochMillis(final DateTimeRange dateRange) {
|
||||
final List<Long> result = new ArrayList<>();
|
||||
|
||||
OffsetDateTime current = dateRange.getStart();
|
||||
final OffsetDateTime end = dateRange.getEnd();
|
||||
current = current.withOffsetSameInstant(ZoneOffset.UTC).withDayOfMonth(1).withHour(0).withMinute(0)
|
||||
.withSecond(0).withNano(0);
|
||||
OffsetDateTime current = dateRange.getStart();
|
||||
final OffsetDateTime end = dateRange.getEnd();
|
||||
current = current.withOffsetSameInstant(ZoneOffset.UTC).withDayOfMonth(1).withHour(0).withMinute(0)
|
||||
.withSecond(0).withNano(0);
|
||||
|
||||
while (!current.isAfter(end)) {
|
||||
result.add(current.toInstant().toEpochMilli());
|
||||
current = current.plusMonths(1);
|
||||
}
|
||||
while (!current.isAfter(end)) {
|
||||
result.add(current.toInstant().toEpochMilli());
|
||||
current = current.plusMonths(1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ParititionId now() {
|
||||
return toPartitionId(System.currentTimeMillis());
|
||||
}
|
||||
public static ParititionId now() {
|
||||
return toPartitionId(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DatePrefixAndRange {
|
||||
private final String datePrefix;
|
||||
private final long minEpochMilli;
|
||||
private final long maxEpochMilli;
|
||||
private final String datePrefix;
|
||||
private final long minEpochMilli;
|
||||
private final long maxEpochMilli;
|
||||
|
||||
public DatePrefixAndRange(final String datePrefix, final long minEpochMilli, final long maxEpochMilli) {
|
||||
super();
|
||||
this.datePrefix = datePrefix;
|
||||
this.minEpochMilli = minEpochMilli;
|
||||
this.maxEpochMilli = maxEpochMilli;
|
||||
}
|
||||
public DatePrefixAndRange(final String datePrefix, final long minEpochMilli, final long maxEpochMilli) {
|
||||
super();
|
||||
this.datePrefix = datePrefix;
|
||||
this.minEpochMilli = minEpochMilli;
|
||||
this.maxEpochMilli = maxEpochMilli;
|
||||
}
|
||||
|
||||
public String getDatePrefix() {
|
||||
return datePrefix;
|
||||
}
|
||||
public String getDatePrefix() {
|
||||
return datePrefix;
|
||||
}
|
||||
|
||||
public long getMinEpochMilli() {
|
||||
return minEpochMilli;
|
||||
}
|
||||
public long getMinEpochMilli() {
|
||||
return minEpochMilli;
|
||||
}
|
||||
|
||||
public long getMaxEpochMilli() {
|
||||
return maxEpochMilli;
|
||||
}
|
||||
public long getMaxEpochMilli() {
|
||||
return maxEpochMilli;
|
||||
}
|
||||
|
||||
public boolean contains(final long epochMilli) {
|
||||
return minEpochMilli <= epochMilli && epochMilli <= maxEpochMilli;
|
||||
}
|
||||
public boolean contains(final long epochMilli) {
|
||||
return minEpochMilli <= epochMilli && epochMilli <= maxEpochMilli;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return datePrefix + " (" + minEpochMilli + " - " + maxEpochMilli + ")";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return datePrefix + " (" + minEpochMilli + " - " + maxEpochMilli + ")";
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,14 @@ import org.lucares.pdb.api.DateTimeRange;
|
||||
|
||||
public class DatePartitioner implements PartitionIdSource {
|
||||
|
||||
private final DateTimeRange dateRange;
|
||||
private final DateTimeRange dateRange;
|
||||
|
||||
public DatePartitioner(final DateTimeRange dateRange) {
|
||||
this.dateRange = dateRange;
|
||||
}
|
||||
public DatePartitioner(final DateTimeRange dateRange) {
|
||||
this.dateRange = dateRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ParititionId> toPartitionIds(final Set<? extends ParititionId> availablePartitions) {
|
||||
return DateIndexExtension.toPartitionIds(dateRange, availablePartitions);
|
||||
}
|
||||
@Override
|
||||
public Set<ParititionId> toPartitionIds(final Set<? extends ParititionId> availablePartitions) {
|
||||
return DateIndexExtension.toPartitionIds(dateRange, availablePartitions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,43 +8,43 @@ import org.lucares.utils.byteencoder.VariableByteEncoder;
|
||||
|
||||
class DocEncoderDecoder implements PartitionAwareEncoderDecoder<Doc, Doc> {
|
||||
|
||||
@Override
|
||||
public byte[] encode(final Doc doc) {
|
||||
@Override
|
||||
public byte[] encode(final Doc doc) {
|
||||
|
||||
final byte[] rootBlockNumber = VariableByteEncoder.encode(doc.getRootBlockNumber());
|
||||
final byte[] tags = doc.getTags().toBytes();
|
||||
final byte[] rootBlockNumber = VariableByteEncoder.encode(doc.getRootBlockNumber());
|
||||
final byte[] tags = doc.getTags().toBytes();
|
||||
|
||||
final byte[] result = new byte[rootBlockNumber.length + tags.length];
|
||||
final byte[] result = new byte[rootBlockNumber.length + tags.length];
|
||||
|
||||
System.arraycopy(rootBlockNumber, 0, result, 0, rootBlockNumber.length);
|
||||
System.arraycopy(tags, 0, result, rootBlockNumber.length, tags.length);
|
||||
System.arraycopy(rootBlockNumber, 0, result, 0, rootBlockNumber.length);
|
||||
System.arraycopy(tags, 0, result, rootBlockNumber.length, tags.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Doc decode(final byte[] bytes) {
|
||||
@Override
|
||||
public Doc decode(final byte[] bytes) {
|
||||
|
||||
final long rootBlockNumber = VariableByteEncoder.decodeFirstValue(bytes);
|
||||
final int bytesRootBlockNumber = VariableByteEncoder.neededBytes(rootBlockNumber);
|
||||
final Tags tags = Tags.fromBytes(Arrays.copyOfRange(bytes, bytesRootBlockNumber, bytes.length));
|
||||
return new Doc(null, tags, rootBlockNumber);
|
||||
}
|
||||
final long rootBlockNumber = VariableByteEncoder.decodeFirstValue(bytes);
|
||||
final int bytesRootBlockNumber = VariableByteEncoder.neededBytes(rootBlockNumber);
|
||||
final Tags tags = Tags.fromBytes(Arrays.copyOfRange(bytes, bytesRootBlockNumber, bytes.length));
|
||||
return new Doc(null, tags, rootBlockNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Doc encodeValue(final Doc v) {
|
||||
return v;
|
||||
}
|
||||
@Override
|
||||
public Doc encodeValue(final Doc v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Doc decodeValue(final ParititionId partitionId, final Doc t) {
|
||||
if (t != null) {
|
||||
t.setPartitionId(partitionId);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] {0};
|
||||
}
|
||||
@Override
|
||||
public Doc decodeValue(final ParititionId partitionId, final Doc t) {
|
||||
if (t != null) {
|
||||
t.setPartitionId(partitionId);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,18 @@ import org.lucares.pdb.datastore.lang.GloblikePattern;
|
||||
|
||||
public class GlobMatcher {
|
||||
|
||||
private final Pattern pattern;
|
||||
private final Pattern pattern;
|
||||
|
||||
public GlobMatcher(final String globlike) {
|
||||
pattern = GloblikePattern.globlikeToRegex(globlike);
|
||||
}
|
||||
public GlobMatcher(final String globlike) {
|
||||
pattern = GloblikePattern.globlikeToRegex(globlike);
|
||||
}
|
||||
|
||||
public GlobMatcher(final Iterable<String> globlikes) {
|
||||
pattern = GloblikePattern.globlikeToRegex(globlikes);
|
||||
}
|
||||
public GlobMatcher(final Iterable<String> globlikes) {
|
||||
pattern = GloblikePattern.globlikeToRegex(globlikes);
|
||||
}
|
||||
|
||||
public boolean matches(final String s) {
|
||||
final Matcher matcher = pattern.matcher(s);
|
||||
return matcher.find();
|
||||
}
|
||||
public boolean matches(final String s) {
|
||||
final Matcher matcher = pattern.matcher(s);
|
||||
return matcher.find();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
package org.lucares.pdb.datastore.internal;
|
||||
|
||||
public class ParititionId implements Comparable<ParititionId> {
|
||||
private final String partitionId;
|
||||
private final String partitionId;
|
||||
|
||||
/**
|
||||
* Create a new partition id.
|
||||
*
|
||||
* @param partitionId the id, e.g. a time like 201902 (partition for entries of
|
||||
* February 2019)
|
||||
*/
|
||||
public ParititionId(final String partitionId) {
|
||||
super();
|
||||
this.partitionId = partitionId;
|
||||
}
|
||||
/**
|
||||
* Create a new partition id.
|
||||
*
|
||||
* @param partitionId the id, e.g. a time like 201902 (partition for entries of
|
||||
* February 2019)
|
||||
*/
|
||||
public ParititionId(final String partitionId) {
|
||||
super();
|
||||
this.partitionId = partitionId;
|
||||
}
|
||||
|
||||
public static ParititionId of(final String partitionId) {
|
||||
return new ParititionId(partitionId);
|
||||
}
|
||||
public static ParititionId of(final String partitionId) {
|
||||
return new ParititionId(partitionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final ParititionId other) {
|
||||
return partitionId.compareTo(other.getPartitionId());
|
||||
}
|
||||
@Override
|
||||
public int compareTo(final ParititionId other) {
|
||||
return partitionId.compareTo(other.getPartitionId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id, e.g. a time like 201902 (partition for entries of February
|
||||
* 2019)
|
||||
*/
|
||||
public String getPartitionId() {
|
||||
return partitionId;
|
||||
}
|
||||
/**
|
||||
* @return the id, e.g. a time like 201902 (partition for entries of February
|
||||
* 2019)
|
||||
*/
|
||||
public String getPartitionId() {
|
||||
return partitionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return partitionId;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return partitionId;
|
||||
}
|
||||
|
||||
/*
|
||||
* non-standard hashcode implementation! This class is just a wrapper for
|
||||
* string, so we delegate directly to String.hashCode().
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return partitionId.hashCode();
|
||||
}
|
||||
/*
|
||||
* non-standard hashcode implementation! This class is just a wrapper for
|
||||
* string, so we delegate directly to String.hashCode().
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return partitionId.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final ParititionId other = (ParititionId) obj;
|
||||
if (partitionId == null) {
|
||||
if (other.partitionId != null)
|
||||
return false;
|
||||
} else if (!partitionId.equals(other.partitionId))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final ParititionId other = (ParititionId) obj;
|
||||
if (partitionId == null) {
|
||||
if (other.partitionId != null)
|
||||
return false;
|
||||
} else if (!partitionId.equals(other.partitionId))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.lucares.pdb.map.PersistentMap.EncoderDecoder;
|
||||
|
||||
public interface PartitionAwareEncoderDecoder<V, P> extends EncoderDecoder<P> {
|
||||
|
||||
public P encodeValue(V v);
|
||||
public P encodeValue(V v);
|
||||
|
||||
public V decodeValue(ParititionId partitionId, P p);
|
||||
public V decodeValue(ParititionId partitionId, P p);
|
||||
}
|
||||
|
||||
@@ -4,37 +4,37 @@ import org.lucares.pdb.map.PersistentMap.EncoderDecoder;
|
||||
|
||||
public final class PartitionAwareWrapper<O> implements PartitionAwareEncoderDecoder<O, O> {
|
||||
|
||||
private final EncoderDecoder<O> delegate;
|
||||
private final EncoderDecoder<O> delegate;
|
||||
|
||||
public PartitionAwareWrapper(final EncoderDecoder<O> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
public PartitionAwareWrapper(final EncoderDecoder<O> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(final O object) {
|
||||
return delegate.encode(object);
|
||||
}
|
||||
@Override
|
||||
public byte[] encode(final O object) {
|
||||
return delegate.encode(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public O decode(final byte[] bytes) {
|
||||
return delegate.decode(bytes);
|
||||
}
|
||||
@Override
|
||||
public O decode(final byte[] bytes) {
|
||||
return delegate.decode(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public O encodeValue(final O v) {
|
||||
return v;
|
||||
}
|
||||
@Override
|
||||
public O encodeValue(final O v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public O decodeValue(final ParititionId partitionId, final O p) {
|
||||
return p;
|
||||
}
|
||||
@Override
|
||||
public O decodeValue(final ParititionId partitionId, final O p) {
|
||||
return p;
|
||||
}
|
||||
|
||||
public static <O> PartitionAwareEncoderDecoder<O, O> wrap(final EncoderDecoder<O> encoder) {
|
||||
return new PartitionAwareWrapper<>(encoder);
|
||||
}
|
||||
|
||||
public byte[] getEmptyValue() {
|
||||
return delegate.getEmptyValue();
|
||||
}
|
||||
public static <O> PartitionAwareEncoderDecoder<O, O> wrap(final EncoderDecoder<O> encoder) {
|
||||
return new PartitionAwareWrapper<>(encoder);
|
||||
}
|
||||
|
||||
public byte[] getEmptyValue() {
|
||||
return delegate.getEmptyValue();
|
||||
}
|
||||
}
|
||||
@@ -14,68 +14,68 @@ import org.lucares.pdb.blockstorage.LongStreamFile;
|
||||
import org.lucares.pdb.diskstorage.DiskStorage;
|
||||
|
||||
public class PartitionDiskStore {
|
||||
private final ConcurrentHashMap<ParititionId, DiskStorage> diskStorages = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<ParititionId, DiskStorage> diskStorages = new ConcurrentHashMap<>();
|
||||
|
||||
private final Function<ParititionId, DiskStorage> creator;
|
||||
private final Function<ParititionId, DiskStorage> supplier;
|
||||
private final Function<ParititionId, DiskStorage> creator;
|
||||
private final Function<ParititionId, DiskStorage> supplier;
|
||||
|
||||
public PartitionDiskStore(final Path storageBasePath, final String filename) {
|
||||
public PartitionDiskStore(final Path storageBasePath, final String filename) {
|
||||
|
||||
creator = partitionId -> {
|
||||
final Path file = storageBasePath.resolve(partitionId.getPartitionId()).resolve(filename);
|
||||
final boolean isNew = !Files.exists(file);
|
||||
final DiskStorage diskStorage = new DiskStorage(file, storageBasePath);
|
||||
if (isNew) {
|
||||
diskStorage.ensureAlignmentForNewBlocks(BSFile.BLOCK_SIZE);
|
||||
}
|
||||
return diskStorage;
|
||||
};
|
||||
supplier = partitionId -> {
|
||||
final Path file = storageBasePath.resolve(partitionId.getPartitionId()).resolve(filename);
|
||||
if (Files.exists(file)) {
|
||||
return new DiskStorage(file, storageBasePath);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
creator = partitionId -> {
|
||||
final Path file = storageBasePath.resolve(partitionId.getPartitionId()).resolve(filename);
|
||||
final boolean isNew = !Files.exists(file);
|
||||
final DiskStorage diskStorage = new DiskStorage(file, storageBasePath);
|
||||
if (isNew) {
|
||||
diskStorage.ensureAlignmentForNewBlocks(BSFile.BLOCK_SIZE);
|
||||
}
|
||||
return diskStorage;
|
||||
};
|
||||
supplier = partitionId -> {
|
||||
final Path file = storageBasePath.resolve(partitionId.getPartitionId()).resolve(filename);
|
||||
if (Files.exists(file)) {
|
||||
return new DiskStorage(file, storageBasePath);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
public DiskStorage getExisting(final ParititionId partitionId) {
|
||||
return diskStorages.computeIfAbsent(partitionId, supplier);
|
||||
}
|
||||
public DiskStorage getExisting(final ParititionId partitionId) {
|
||||
return diskStorages.computeIfAbsent(partitionId, supplier);
|
||||
}
|
||||
|
||||
public DiskStorage getCreateIfNotExists(final ParititionId partitionId) {
|
||||
return diskStorages.computeIfAbsent(partitionId, creator);
|
||||
}
|
||||
public DiskStorage getCreateIfNotExists(final ParititionId partitionId) {
|
||||
return diskStorages.computeIfAbsent(partitionId, creator);
|
||||
}
|
||||
|
||||
public long allocateBlock(final ParititionId partitionId, final int blockSize) {
|
||||
final DiskStorage diskStorage = getCreateIfNotExists(partitionId);
|
||||
return diskStorage.allocateBlock(blockSize);
|
||||
}
|
||||
public long allocateBlock(final ParititionId partitionId, final int blockSize) {
|
||||
final DiskStorage diskStorage = getCreateIfNotExists(partitionId);
|
||||
return diskStorage.allocateBlock(blockSize);
|
||||
}
|
||||
|
||||
public LongStreamFile streamExistingFile(final Long diskStoreOffsetForDocIdsOfTag, final ParititionId partitionId) {
|
||||
try {
|
||||
final DiskStorage diskStorage = getExisting(partitionId);
|
||||
return LongStreamFile.existingFile(diskStoreOffsetForDocIdsOfTag, diskStorage);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
public LongStreamFile streamExistingFile(final Long diskStoreOffsetForDocIdsOfTag, final ParititionId partitionId) {
|
||||
try {
|
||||
final DiskStorage diskStorage = getExisting(partitionId);
|
||||
return LongStreamFile.existingFile(diskStoreOffsetForDocIdsOfTag, diskStorage);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
final List<Throwable> throwables = new ArrayList<>();
|
||||
public void close() {
|
||||
final List<Throwable> throwables = new ArrayList<>();
|
||||
|
||||
for (final DiskStorage diskStorage : diskStorages.values()) {
|
||||
try {
|
||||
diskStorage.close();
|
||||
} catch (final RuntimeException e) {
|
||||
throwables.add(e);
|
||||
}
|
||||
}
|
||||
if (!throwables.isEmpty()) {
|
||||
final RuntimeException ex = new RuntimeException();
|
||||
throwables.forEach(ex::addSuppressed);
|
||||
throw ex;
|
||||
}
|
||||
for (final DiskStorage diskStorage : diskStorages.values()) {
|
||||
try {
|
||||
diskStorage.close();
|
||||
} catch (final RuntimeException e) {
|
||||
throwables.add(e);
|
||||
}
|
||||
}
|
||||
if (!throwables.isEmpty()) {
|
||||
final RuntimeException ex = new RuntimeException();
|
||||
throwables.forEach(ex::addSuppressed);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ package org.lucares.pdb.datastore.internal;
|
||||
import java.util.Set;
|
||||
|
||||
public interface PartitionIdSource {
|
||||
Set<ParititionId> toPartitionIds(Set<? extends ParititionId> availablePartitions);
|
||||
Set<ParititionId> toPartitionIds(Set<? extends ParititionId> availablePartitions);
|
||||
}
|
||||
|
||||
@@ -9,87 +9,87 @@ import java.util.Set;
|
||||
import org.lucares.collections.LongList;
|
||||
|
||||
public class PartitionLongList implements Iterable<ParititionId> {
|
||||
private final Map<ParititionId, LongList> lists = new HashMap<>();
|
||||
private final Map<ParititionId, LongList> lists = new HashMap<>();
|
||||
|
||||
public LongList put(final ParititionId partitionId, final LongList longList) {
|
||||
return lists.put(partitionId, longList);
|
||||
}
|
||||
public LongList put(final ParititionId partitionId, final LongList longList) {
|
||||
return lists.put(partitionId, longList);
|
||||
}
|
||||
|
||||
public LongList get(final ParititionId partitionId) {
|
||||
return lists.get(partitionId);
|
||||
}
|
||||
public LongList get(final ParititionId partitionId) {
|
||||
return lists.get(partitionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ParititionId> iterator() {
|
||||
return lists.keySet().iterator();
|
||||
}
|
||||
@Override
|
||||
public Iterator<ParititionId> iterator() {
|
||||
return lists.keySet().iterator();
|
||||
}
|
||||
|
||||
public static PartitionLongList intersection(final PartitionLongList a, final PartitionLongList b) {
|
||||
final PartitionLongList result = new PartitionLongList();
|
||||
final Set<ParititionId> partitionIds = new HashSet<>();
|
||||
partitionIds.addAll(a.lists.keySet());
|
||||
partitionIds.addAll(b.lists.keySet());
|
||||
public static PartitionLongList intersection(final PartitionLongList a, final PartitionLongList b) {
|
||||
final PartitionLongList result = new PartitionLongList();
|
||||
final Set<ParititionId> partitionIds = new HashSet<>();
|
||||
partitionIds.addAll(a.lists.keySet());
|
||||
partitionIds.addAll(b.lists.keySet());
|
||||
|
||||
for (final ParititionId partitionId : partitionIds) {
|
||||
final LongList x = a.get(partitionId);
|
||||
final LongList y = b.get(partitionId);
|
||||
for (final ParititionId partitionId : partitionIds) {
|
||||
final LongList x = a.get(partitionId);
|
||||
final LongList y = b.get(partitionId);
|
||||
|
||||
if (x != null && y != null) {
|
||||
final LongList intersection = LongList.intersection(x, y);
|
||||
result.put(partitionId, intersection);
|
||||
} else {
|
||||
// one list is empty => the intersection is empty
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (x != null && y != null) {
|
||||
final LongList intersection = LongList.intersection(x, y);
|
||||
result.put(partitionId, intersection);
|
||||
} else {
|
||||
// one list is empty => the intersection is empty
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static PartitionLongList union(final PartitionLongList a, final PartitionLongList b) {
|
||||
final PartitionLongList result = new PartitionLongList();
|
||||
final Set<ParititionId> partitionIds = new HashSet<>();
|
||||
partitionIds.addAll(a.lists.keySet());
|
||||
partitionIds.addAll(b.lists.keySet());
|
||||
for (final ParititionId partitionId : partitionIds) {
|
||||
final LongList x = a.get(partitionId);
|
||||
final LongList y = b.get(partitionId);
|
||||
public static PartitionLongList union(final PartitionLongList a, final PartitionLongList b) {
|
||||
final PartitionLongList result = new PartitionLongList();
|
||||
final Set<ParititionId> partitionIds = new HashSet<>();
|
||||
partitionIds.addAll(a.lists.keySet());
|
||||
partitionIds.addAll(b.lists.keySet());
|
||||
for (final ParititionId partitionId : partitionIds) {
|
||||
final LongList x = a.get(partitionId);
|
||||
final LongList y = b.get(partitionId);
|
||||
|
||||
if (x != null && y != null) {
|
||||
final LongList intersection = LongList.union(x, y);
|
||||
result.put(partitionId, intersection);
|
||||
} else if (x != null) {
|
||||
result.put(partitionId, x.clone());
|
||||
} else if (y != null) {
|
||||
result.put(partitionId, y.clone());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (x != null && y != null) {
|
||||
final LongList intersection = LongList.union(x, y);
|
||||
result.put(partitionId, intersection);
|
||||
} else if (x != null) {
|
||||
result.put(partitionId, x.clone());
|
||||
} else if (y != null) {
|
||||
result.put(partitionId, y.clone());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
int size = 0;
|
||||
public int size() {
|
||||
int size = 0;
|
||||
|
||||
for (final LongList longList : lists.values()) {
|
||||
size += longList.size();
|
||||
}
|
||||
for (final LongList longList : lists.values()) {
|
||||
size += longList.size();
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public boolean isSorted() {
|
||||
for (final LongList longList : lists.values()) {
|
||||
if (!longList.isSorted()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public boolean isSorted() {
|
||||
for (final LongList longList : lists.values()) {
|
||||
if (!longList.isSorted()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void removeAll(final PartitionLongList remove) {
|
||||
for (final ParititionId partitionId : lists.keySet()) {
|
||||
final LongList removeLongList = remove.get(partitionId);
|
||||
if (removeLongList != null) {
|
||||
lists.get(partitionId).removeAll(removeLongList);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void removeAll(final PartitionLongList remove) {
|
||||
for (final ParititionId partitionId : lists.keySet()) {
|
||||
final LongList removeLongList = remove.get(partitionId);
|
||||
if (removeLongList != null) {
|
||||
lists.get(partitionId).removeAll(removeLongList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,130 +25,130 @@ import org.lucares.pdb.map.Visitor;
|
||||
*/
|
||||
public class PartitionPersistentMap<K, V, P> implements AutoCloseable {
|
||||
|
||||
private final ConcurrentHashMap<ParititionId, PersistentMap<K, P>> maps = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<ParititionId, PersistentMap<K, P>> maps = new ConcurrentHashMap<>();
|
||||
|
||||
private final Function<ParititionId, PersistentMap<K, P>> creator;
|
||||
private final Function<ParititionId, PersistentMap<K, P>> supplier;
|
||||
private final Function<ParititionId, PersistentMap<K, P>> creator;
|
||||
private final Function<ParititionId, PersistentMap<K, P>> supplier;
|
||||
|
||||
private final PartitionAwareEncoderDecoder<V, P> valueEncoder;
|
||||
private final PartitionAwareEncoderDecoder<V, P> valueEncoder;
|
||||
|
||||
public PartitionPersistentMap(final Path storageBasePath, final String filename, final EncoderDecoder<K> keyEncoder,
|
||||
final PartitionAwareEncoderDecoder<V, P> valueEncoder) {
|
||||
public PartitionPersistentMap(final Path storageBasePath, final String filename, final EncoderDecoder<K> keyEncoder,
|
||||
final PartitionAwareEncoderDecoder<V, P> valueEncoder) {
|
||||
|
||||
this.valueEncoder = valueEncoder;
|
||||
creator = partitionId -> {
|
||||
final Path file = storageBasePath.resolve(partitionId.getPartitionId()).resolve(filename);
|
||||
return new PersistentMap<>(file, storageBasePath, keyEncoder, valueEncoder);
|
||||
};
|
||||
supplier = partitionId -> {
|
||||
final Path file = storageBasePath.resolve(partitionId.getPartitionId()).resolve(filename);
|
||||
if (Files.exists(file)) {
|
||||
return new PersistentMap<>(file, storageBasePath, keyEncoder, valueEncoder);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
preload(storageBasePath);
|
||||
}
|
||||
this.valueEncoder = valueEncoder;
|
||||
creator = partitionId -> {
|
||||
final Path file = storageBasePath.resolve(partitionId.getPartitionId()).resolve(filename);
|
||||
return new PersistentMap<>(file, storageBasePath, keyEncoder, valueEncoder);
|
||||
};
|
||||
supplier = partitionId -> {
|
||||
final Path file = storageBasePath.resolve(partitionId.getPartitionId()).resolve(filename);
|
||||
if (Files.exists(file)) {
|
||||
return new PersistentMap<>(file, storageBasePath, keyEncoder, valueEncoder);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
preload(storageBasePath);
|
||||
}
|
||||
|
||||
private void preload(final Path storageBasePath) {
|
||||
try {
|
||||
Files.list(storageBasePath)//
|
||||
.filter(Files::isDirectory)//
|
||||
.map(Path::getFileName)//
|
||||
.map(Path::toString)//
|
||||
.map(ParititionId::of)//
|
||||
.forEach(partitionId -> maps.computeIfAbsent(partitionId, supplier));
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
private void preload(final Path storageBasePath) {
|
||||
try {
|
||||
Files.list(storageBasePath)//
|
||||
.filter(Files::isDirectory)//
|
||||
.map(Path::getFileName)//
|
||||
.map(Path::toString)//
|
||||
.map(ParititionId::of)//
|
||||
.forEach(partitionId -> maps.computeIfAbsent(partitionId, supplier));
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<ParititionId> getAllPartitionIds() {
|
||||
return maps.keySet();
|
||||
}
|
||||
private Set<ParititionId> getAllPartitionIds() {
|
||||
return maps.keySet();
|
||||
}
|
||||
|
||||
public Set<ParititionId> getAvailablePartitionIds(final PartitionIdSource partitionIdSource) {
|
||||
return partitionIdSource.toPartitionIds(getAllPartitionIds());
|
||||
}
|
||||
public Set<ParititionId> getAvailablePartitionIds(final PartitionIdSource partitionIdSource) {
|
||||
return partitionIdSource.toPartitionIds(getAllPartitionIds());
|
||||
}
|
||||
|
||||
private PersistentMap<K, P> getExistingPersistentMap(final ParititionId partitionId) {
|
||||
return maps.computeIfAbsent(partitionId, supplier);
|
||||
}
|
||||
private PersistentMap<K, P> getExistingPersistentMap(final ParititionId partitionId) {
|
||||
return maps.computeIfAbsent(partitionId, supplier);
|
||||
}
|
||||
|
||||
private PersistentMap<K, P> getPersistentMapCreateIfNotExists(final ParititionId partitionId) {
|
||||
return maps.computeIfAbsent(partitionId, creator);
|
||||
}
|
||||
private PersistentMap<K, P> getPersistentMapCreateIfNotExists(final ParititionId partitionId) {
|
||||
return maps.computeIfAbsent(partitionId, creator);
|
||||
}
|
||||
|
||||
public V getValue(final ParititionId partitionId, final K key) {
|
||||
final PersistentMap<K, P> map = getExistingPersistentMap(partitionId);
|
||||
final P persistedValue = map != null ? map.getValue(key) : null;
|
||||
return valueEncoder.decodeValue(partitionId, persistedValue);
|
||||
}
|
||||
public V getValue(final ParititionId partitionId, final K key) {
|
||||
final PersistentMap<K, P> map = getExistingPersistentMap(partitionId);
|
||||
final P persistedValue = map != null ? map.getValue(key) : null;
|
||||
return valueEncoder.decodeValue(partitionId, persistedValue);
|
||||
}
|
||||
|
||||
public List<V> getValues(final PartitionIdSource partitionIdSource, final K key) {
|
||||
final List<V> result = new ArrayList<>();
|
||||
final Set<ParititionId> partitionIds = partitionIdSource.toPartitionIds(getAllPartitionIds());
|
||||
public List<V> getValues(final PartitionIdSource partitionIdSource, final K key) {
|
||||
final List<V> result = new ArrayList<>();
|
||||
final Set<ParititionId> partitionIds = partitionIdSource.toPartitionIds(getAllPartitionIds());
|
||||
|
||||
for (final ParititionId partitionId : partitionIds) {
|
||||
final PersistentMap<K, P> map = getPersistentMapCreateIfNotExists(partitionId);
|
||||
if (map != null) {
|
||||
final V value = valueEncoder.decodeValue(partitionId, map.getValue(key));
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final ParititionId partitionId : partitionIds) {
|
||||
final PersistentMap<K, P> map = getPersistentMapCreateIfNotExists(partitionId);
|
||||
if (map != null) {
|
||||
final V value = valueEncoder.decodeValue(partitionId, map.getValue(key));
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public V putValue(final ParititionId partitionId, final K key, final V value) {
|
||||
final PersistentMap<K, P> map = getPersistentMapCreateIfNotExists(partitionId);
|
||||
final P persistedValue = valueEncoder.encodeValue(value);
|
||||
final P previousPersistedValue = map.putValue(key, persistedValue);
|
||||
return valueEncoder.decodeValue(partitionId, previousPersistedValue);
|
||||
}
|
||||
public V putValue(final ParititionId partitionId, final K key, final V value) {
|
||||
final PersistentMap<K, P> map = getPersistentMapCreateIfNotExists(partitionId);
|
||||
final P persistedValue = valueEncoder.encodeValue(value);
|
||||
final P previousPersistedValue = map.putValue(key, persistedValue);
|
||||
return valueEncoder.decodeValue(partitionId, previousPersistedValue);
|
||||
}
|
||||
|
||||
public void visitValues(final ParititionId partitionId, final K keyPrefix, final Visitor<K, V> visitor) {
|
||||
final PersistentMap<K, P> map = getExistingPersistentMap(partitionId);
|
||||
if (map != null) {
|
||||
map.visitValues(keyPrefix, (k, p) -> {
|
||||
final V value = valueEncoder.decodeValue(partitionId, p);
|
||||
visitor.visit(k, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
public void visitValues(final ParititionId partitionId, final K keyPrefix, final Visitor<K, V> visitor) {
|
||||
final PersistentMap<K, P> map = getExistingPersistentMap(partitionId);
|
||||
if (map != null) {
|
||||
map.visitValues(keyPrefix, (k, p) -> {
|
||||
final V value = valueEncoder.decodeValue(partitionId, p);
|
||||
visitor.visit(k, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void visitValues(final PartitionIdSource partitionIdSource, final K keyPrefix, final Visitor<K, V> visitor) {
|
||||
final Set<ParititionId> partitionIds = partitionIdSource.toPartitionIds(getAllPartitionIds());
|
||||
public void visitValues(final PartitionIdSource partitionIdSource, final K keyPrefix, final Visitor<K, V> visitor) {
|
||||
final Set<ParititionId> partitionIds = partitionIdSource.toPartitionIds(getAllPartitionIds());
|
||||
|
||||
for (final ParititionId partitionId : partitionIds) {
|
||||
final PersistentMap<K, P> map = getExistingPersistentMap(partitionId);
|
||||
if (map != null) {
|
||||
map.visitValues(keyPrefix, (k, p) -> {
|
||||
final V value = valueEncoder.decodeValue(partitionId, p);
|
||||
visitor.visit(k, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final ParititionId partitionId : partitionIds) {
|
||||
final PersistentMap<K, P> map = getExistingPersistentMap(partitionId);
|
||||
if (map != null) {
|
||||
map.visitValues(keyPrefix, (k, p) -> {
|
||||
final V value = valueEncoder.decodeValue(partitionId, p);
|
||||
visitor.visit(k, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
final List<Throwable> throwables = new ArrayList<>();
|
||||
@Override
|
||||
public void close() {
|
||||
final List<Throwable> throwables = new ArrayList<>();
|
||||
|
||||
for (final PersistentMap<K, P> map : maps.values()) {
|
||||
try {
|
||||
map.close();
|
||||
} catch (final RuntimeException e) {
|
||||
throwables.add(e);
|
||||
}
|
||||
}
|
||||
if (!throwables.isEmpty()) {
|
||||
final RuntimeException ex = new RuntimeException();
|
||||
throwables.forEach(ex::addSuppressed);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
for (final PersistentMap<K, P> map : maps.values()) {
|
||||
try {
|
||||
map.close();
|
||||
} catch (final RuntimeException e) {
|
||||
throwables.add(e);
|
||||
}
|
||||
}
|
||||
if (!throwables.isEmpty()) {
|
||||
final RuntimeException ex = new RuntimeException();
|
||||
throwables.forEach(ex::addSuppressed);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,62 +17,62 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
class PdbWriter implements AutoCloseable, Flushable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PdbWriter.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PdbWriter.class);
|
||||
|
||||
private final PdbFile pdbFile;
|
||||
private long lastEpochMilli;
|
||||
private final PdbFile pdbFile;
|
||||
private long lastEpochMilli;
|
||||
|
||||
private final TimeSeriesFile timeSeriesFile;
|
||||
private final TimeSeriesFile timeSeriesFile;
|
||||
|
||||
public PdbWriter(final PdbFile pdbFile, final DiskStorage diskStorage) {
|
||||
this.pdbFile = pdbFile;
|
||||
public PdbWriter(final PdbFile pdbFile, final DiskStorage diskStorage) {
|
||||
this.pdbFile = pdbFile;
|
||||
|
||||
timeSeriesFile = TimeSeriesFile.existingFile(pdbFile.getRootBlockNumber(), diskStorage);
|
||||
final Optional<Long> optionalLastValue = timeSeriesFile.getLastValue(); // TODO is this last value correct?
|
||||
timeSeriesFile = TimeSeriesFile.existingFile(pdbFile.getRootBlockNumber(), diskStorage);
|
||||
final Optional<Long> optionalLastValue = timeSeriesFile.getLastValue(); // TODO is this last value correct?
|
||||
|
||||
lastEpochMilli = optionalLastValue.orElse(0L);
|
||||
}
|
||||
lastEpochMilli = optionalLastValue.orElse(0L);
|
||||
}
|
||||
|
||||
public PdbFile getPdbFile() {
|
||||
return pdbFile;
|
||||
}
|
||||
public PdbFile getPdbFile() {
|
||||
return pdbFile;
|
||||
}
|
||||
|
||||
public long getDateOffsetAsEpochMilli() {
|
||||
return lastEpochMilli;
|
||||
}
|
||||
public long getDateOffsetAsEpochMilli() {
|
||||
return lastEpochMilli;
|
||||
}
|
||||
|
||||
public void write(final long epochMilli, final long value) throws WriteException, InvalidValueException {
|
||||
try {
|
||||
timeSeriesFile.appendTimeValue(epochMilli, value);
|
||||
public void write(final long epochMilli, final long value) throws WriteException, InvalidValueException {
|
||||
try {
|
||||
timeSeriesFile.appendTimeValue(epochMilli, value);
|
||||
|
||||
lastEpochMilli = epochMilli;
|
||||
} catch (final RuntimeException e) {
|
||||
throw new WriteException(e);
|
||||
}
|
||||
}
|
||||
lastEpochMilli = epochMilli;
|
||||
} catch (final RuntimeException e) {
|
||||
throw new WriteException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
LOGGER.debug("close PdbWriter {}", pdbFile);
|
||||
timeSeriesFile.close();
|
||||
}
|
||||
LOGGER.debug("close PdbWriter {}", pdbFile);
|
||||
timeSeriesFile.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
timeSeriesFile.flush();
|
||||
}
|
||||
@Override
|
||||
public void flush() {
|
||||
timeSeriesFile.flush();
|
||||
}
|
||||
|
||||
public static void writeEntry(final PdbFile pdbFile, final DiskStorage diskStorage, final Entry... entries) {
|
||||
try (PdbWriter writer = new PdbWriter(pdbFile, diskStorage)) {
|
||||
for (final Entry entry : entries) {
|
||||
writer.write(entry.getEpochMilli(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void writeEntry(final PdbFile pdbFile, final DiskStorage diskStorage, final Entry... entries) {
|
||||
try (PdbWriter writer = new PdbWriter(pdbFile, diskStorage)) {
|
||||
for (final Entry entry : entries) {
|
||||
writer.write(entry.getEpochMilli(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PdbWriter [pdbFile=" + pdbFile + ", lastEpochMilli=" + lastEpochMilli + "]";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PdbWriter [pdbFile=" + pdbFile + ", lastEpochMilli=" + lastEpochMilli + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,321 +143,321 @@ import org.lucares.utils.byteencoder.VariableByteEncoder;
|
||||
*
|
||||
*/
|
||||
public class QueryCompletionIndex implements AutoCloseable {
|
||||
private static final class TwoTags {
|
||||
private final Tag tagA;
|
||||
private final Tag tagB;
|
||||
private static final class TwoTags {
|
||||
private final Tag tagA;
|
||||
private final Tag tagB;
|
||||
|
||||
public TwoTags(final Tag tagA, final Tag tagB) {
|
||||
this.tagA = tagA;
|
||||
this.tagB = tagB;
|
||||
}
|
||||
public TwoTags(final Tag tagA, final Tag tagB) {
|
||||
this.tagA = tagA;
|
||||
this.tagB = tagB;
|
||||
}
|
||||
|
||||
public TwoTags(final String fieldB, final String fieldA, final String valueA, final String valueB) {
|
||||
public TwoTags(final String fieldB, final String fieldA, final String valueA, final String valueB) {
|
||||
|
||||
tagA = new Tag(fieldA, valueA);
|
||||
tagB = new Tag(fieldB, valueB);
|
||||
}
|
||||
tagA = new Tag(fieldA, valueA);
|
||||
tagB = new Tag(fieldB, valueB);
|
||||
}
|
||||
|
||||
public Tag getTagA() {
|
||||
return tagA;
|
||||
}
|
||||
public Tag getTagA() {
|
||||
return tagA;
|
||||
}
|
||||
|
||||
public Tag getTagB() {
|
||||
return tagB;
|
||||
}
|
||||
public Tag getTagB() {
|
||||
return tagB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return tagA + "::" + tagB;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return tagA + "::" + tagB;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class FieldField {
|
||||
private final int fieldA;
|
||||
private final int fieldB;
|
||||
public static final class FieldField {
|
||||
private final int fieldA;
|
||||
private final int fieldB;
|
||||
|
||||
public FieldField(final int fieldA, final int fieldB) {
|
||||
this.fieldA = fieldA;
|
||||
this.fieldB = fieldB;
|
||||
}
|
||||
public FieldField(final int fieldA, final int fieldB) {
|
||||
this.fieldA = fieldA;
|
||||
this.fieldB = fieldB;
|
||||
}
|
||||
|
||||
public int getFieldA() {
|
||||
return fieldA;
|
||||
}
|
||||
public int getFieldA() {
|
||||
return fieldA;
|
||||
}
|
||||
|
||||
public int getFieldB() {
|
||||
return fieldB;
|
||||
}
|
||||
public int getFieldB() {
|
||||
return fieldB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fieldA + "::" + fieldB;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return fieldA + "::" + fieldB;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EncoderTwoTags implements EncoderDecoder<TwoTags> {
|
||||
private static final class EncoderTwoTags implements EncoderDecoder<TwoTags> {
|
||||
|
||||
@Override
|
||||
public byte[] encode(final TwoTags tagAndField) {
|
||||
final LongList tmp = new LongList(4);
|
||||
final Tag tagA = tagAndField.getTagA();
|
||||
final Tag tagB = tagAndField.getTagB();
|
||||
@Override
|
||||
public byte[] encode(final TwoTags tagAndField) {
|
||||
final LongList tmp = new LongList(4);
|
||||
final Tag tagA = tagAndField.getTagA();
|
||||
final Tag tagB = tagAndField.getTagB();
|
||||
|
||||
tmp.add(tagB.getKey());
|
||||
tmp.add(tagA.getKey());
|
||||
tmp.add(tagB.getKey());
|
||||
tmp.add(tagA.getKey());
|
||||
|
||||
if (tagA.getValue() >= 0) {
|
||||
tmp.add(tagA.getValue());
|
||||
if (tagA.getValue() >= 0) {
|
||||
tmp.add(tagA.getValue());
|
||||
|
||||
// A query for tagA.key and tagA.value and tagB.key is done by setting
|
||||
// tagB.value==-1.
|
||||
// The query is then executed as a prefix search. Thus tagB.value must not be
|
||||
// part of the byte array that is returned.
|
||||
if (tagB.getValue() >= 0) {
|
||||
tmp.add(tagB.getValue());
|
||||
}
|
||||
} else {
|
||||
Preconditions.checkSmaller(tagB.getValue(), 0,
|
||||
"if no value for tagA is given, then tagB must also be empty");
|
||||
}
|
||||
// A query for tagA.key and tagA.value and tagB.key is done by setting
|
||||
// tagB.value==-1.
|
||||
// The query is then executed as a prefix search. Thus tagB.value must not be
|
||||
// part of the byte array that is returned.
|
||||
if (tagB.getValue() >= 0) {
|
||||
tmp.add(tagB.getValue());
|
||||
}
|
||||
} else {
|
||||
Preconditions.checkSmaller(tagB.getValue(), 0,
|
||||
"if no value for tagA is given, then tagB must also be empty");
|
||||
}
|
||||
|
||||
return VariableByteEncoder.encode(tmp);
|
||||
}
|
||||
return VariableByteEncoder.encode(tmp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TwoTags decode(final byte[] bytes) {
|
||||
@Override
|
||||
public TwoTags decode(final byte[] bytes) {
|
||||
|
||||
final LongList tmp = VariableByteEncoder.decode(bytes);
|
||||
final int tagBKey = (int) tmp.get(0);
|
||||
final int tagAKey = (int) tmp.get(1);
|
||||
final int tagAValue = (int) tmp.get(2);
|
||||
final int tagBValue = (int) tmp.get(3);
|
||||
final LongList tmp = VariableByteEncoder.decode(bytes);
|
||||
final int tagBKey = (int) tmp.get(0);
|
||||
final int tagAKey = (int) tmp.get(1);
|
||||
final int tagAValue = (int) tmp.get(2);
|
||||
final int tagBValue = (int) tmp.get(3);
|
||||
|
||||
final Tag tagA = new Tag(tagAKey, tagAValue);
|
||||
final Tag tagB = new Tag(tagBKey, tagBValue);
|
||||
final Tag tagA = new Tag(tagAKey, tagAValue);
|
||||
final Tag tagB = new Tag(tagBKey, tagBValue);
|
||||
|
||||
return new TwoTags(tagA, tagB);
|
||||
}
|
||||
return new TwoTags(tagA, tagB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] { 0, 0, 0, 0 };
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] { 0, 0, 0, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EncoderTag implements EncoderDecoder<Tag> {
|
||||
private static final class EncoderTag implements EncoderDecoder<Tag> {
|
||||
|
||||
@Override
|
||||
public byte[] encode(final Tag tag) {
|
||||
@Override
|
||||
public byte[] encode(final Tag tag) {
|
||||
|
||||
final LongList longList = new LongList(2);
|
||||
longList.add(tag.getKey());
|
||||
final LongList longList = new LongList(2);
|
||||
longList.add(tag.getKey());
|
||||
|
||||
if (tag.getValue() >= 0) {
|
||||
longList.add(tag.getValue());
|
||||
}
|
||||
return VariableByteEncoder.encode(longList);
|
||||
}
|
||||
if (tag.getValue() >= 0) {
|
||||
longList.add(tag.getValue());
|
||||
}
|
||||
return VariableByteEncoder.encode(longList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag decode(final byte[] bytes) {
|
||||
final LongList tmp = VariableByteEncoder.decode(bytes);
|
||||
final int key = (int) tmp.get(0);
|
||||
final int value = (int) tmp.get(1);
|
||||
return new Tag(key, value);
|
||||
}
|
||||
@Override
|
||||
public Tag decode(final byte[] bytes) {
|
||||
final LongList tmp = VariableByteEncoder.decode(bytes);
|
||||
final int key = (int) tmp.get(0);
|
||||
final int value = (int) tmp.get(1);
|
||||
return new Tag(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] { 0 };
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EncoderField implements EncoderDecoder<String> {
|
||||
private static final class EncoderField implements EncoderDecoder<String> {
|
||||
|
||||
@Override
|
||||
public byte[] encode(final String field) {
|
||||
@Override
|
||||
public byte[] encode(final String field) {
|
||||
|
||||
if (field.isEmpty()) {
|
||||
return new byte[0];
|
||||
}
|
||||
if (field.isEmpty()) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
return VariableByteEncoder.encode(Tags.STRING_COMPRESSOR.put(field));
|
||||
}
|
||||
return VariableByteEncoder.encode(Tags.STRING_COMPRESSOR.put(field));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(final byte[] bytes) {
|
||||
final long compressedString = VariableByteEncoder.decodeFirstValue(bytes);
|
||||
return Tags.STRING_COMPRESSOR.get((int) compressedString);
|
||||
}
|
||||
@Override
|
||||
public String decode(final byte[] bytes) {
|
||||
final long compressedString = VariableByteEncoder.decodeFirstValue(bytes);
|
||||
return Tags.STRING_COMPRESSOR.get((int) compressedString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] { 0 };
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
private final PartitionPersistentMap<TwoTags, Empty, Empty> tagToTagIndex;
|
||||
private final PartitionPersistentMap<Tag, Empty, Empty> fieldToValueIndex;
|
||||
private final PartitionPersistentMap<String, Empty, Empty> fieldIndex;
|
||||
private final PartitionPersistentMap<TwoTags, Empty, Empty> tagToTagIndex;
|
||||
private final PartitionPersistentMap<Tag, Empty, Empty> fieldToValueIndex;
|
||||
private final PartitionPersistentMap<String, Empty, Empty> fieldIndex;
|
||||
|
||||
public QueryCompletionIndex(final Path basePath) throws IOException {
|
||||
tagToTagIndex = new PartitionPersistentMap<>(basePath, "queryCompletionTagToTagIndex.bs", new EncoderTwoTags(),
|
||||
PartitionAwareWrapper.wrap(PersistentMap.EMPTY_ENCODER));
|
||||
public QueryCompletionIndex(final Path basePath) throws IOException {
|
||||
tagToTagIndex = new PartitionPersistentMap<>(basePath, "queryCompletionTagToTagIndex.bs", new EncoderTwoTags(),
|
||||
PartitionAwareWrapper.wrap(PersistentMap.EMPTY_ENCODER));
|
||||
|
||||
fieldToValueIndex = new PartitionPersistentMap<>(basePath, "queryCompletionFieldToValueIndex.bs",
|
||||
new EncoderTag(), PartitionAwareWrapper.wrap(PersistentMap.EMPTY_ENCODER));
|
||||
fieldToValueIndex = new PartitionPersistentMap<>(basePath, "queryCompletionFieldToValueIndex.bs",
|
||||
new EncoderTag(), PartitionAwareWrapper.wrap(PersistentMap.EMPTY_ENCODER));
|
||||
|
||||
fieldIndex = new PartitionPersistentMap<>(basePath, "queryCompletionFieldIndex.bs", new EncoderField(),
|
||||
PartitionAwareWrapper.wrap(PersistentMap.EMPTY_ENCODER));
|
||||
}
|
||||
fieldIndex = new PartitionPersistentMap<>(basePath, "queryCompletionFieldIndex.bs", new EncoderField(),
|
||||
PartitionAwareWrapper.wrap(PersistentMap.EMPTY_ENCODER));
|
||||
}
|
||||
|
||||
public void addTags(final ParititionId partitionId, final Tags tags) throws IOException {
|
||||
final List<Tag> listOfTagsA = tags.toTags();
|
||||
final List<Tag> listOfTagsB = tags.toTags();
|
||||
public void addTags(final ParititionId partitionId, final Tags tags) throws IOException {
|
||||
final List<Tag> listOfTagsA = tags.toTags();
|
||||
final List<Tag> listOfTagsB = tags.toTags();
|
||||
|
||||
// index all combinations of tagA and tagB and fieldA to fieldB
|
||||
for (final Tag tagA : listOfTagsA) {
|
||||
for (final Tag tagB : listOfTagsB) {
|
||||
final TwoTags key = new TwoTags(tagA, tagB);
|
||||
tagToTagIndex.putValue(partitionId, key, Empty.INSTANCE);
|
||||
}
|
||||
}
|
||||
// index all combinations of tagA and tagB and fieldA to fieldB
|
||||
for (final Tag tagA : listOfTagsA) {
|
||||
for (final Tag tagB : listOfTagsB) {
|
||||
final TwoTags key = new TwoTags(tagA, tagB);
|
||||
tagToTagIndex.putValue(partitionId, key, Empty.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
// create indices of all tags and all fields
|
||||
for (final Tag tag : listOfTagsA) {
|
||||
fieldToValueIndex.putValue(partitionId, tag, Empty.INSTANCE);
|
||||
fieldIndex.putValue(partitionId, tag.getKeyAsString(), Empty.INSTANCE);
|
||||
}
|
||||
}
|
||||
// create indices of all tags and all fields
|
||||
for (final Tag tag : listOfTagsA) {
|
||||
fieldToValueIndex.putValue(partitionId, tag, Empty.INSTANCE);
|
||||
fieldIndex.putValue(partitionId, tag.getKeyAsString(), Empty.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
tagToTagIndex.close();
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
tagToTagIndex.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find values for fieldB that are yield results when executing the query
|
||||
* "fieldA=valueA and fieldB=???"
|
||||
*
|
||||
* @param dateRange the date range
|
||||
* @param fieldA the other field of the and expression
|
||||
* @param valueA {@link GlobMatcher} for the value of the other field
|
||||
* @param fieldB the field we are searching values for
|
||||
* @return values of fieldB
|
||||
*/
|
||||
public SortedSet<String> find(final DateTimeRange dateRange, final String fieldA, final GlobMatcher valueA,
|
||||
final String fieldB) {
|
||||
/**
|
||||
* Find values for fieldB that are yield results when executing the query
|
||||
* "fieldA=valueA and fieldB=???"
|
||||
*
|
||||
* @param dateRange the date range
|
||||
* @param fieldA the other field of the and expression
|
||||
* @param valueA {@link GlobMatcher} for the value of the other field
|
||||
* @param fieldB the field we are searching values for
|
||||
* @return values of fieldB
|
||||
*/
|
||||
public SortedSet<String> find(final DateTimeRange dateRange, final String fieldA, final GlobMatcher valueA,
|
||||
final String fieldB) {
|
||||
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
|
||||
final TwoTags keyPrefix = new TwoTags(fieldB, fieldA, null, null);
|
||||
final TwoTags keyPrefix = new TwoTags(fieldB, fieldA, null, null);
|
||||
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
tagToTagIndex.visitValues(partitionIdSource, keyPrefix, (k, v) -> {
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
tagToTagIndex.visitValues(partitionIdSource, keyPrefix, (k, v) -> {
|
||||
|
||||
final String vA = k.getTagA().getValueAsString();
|
||||
final String vA = k.getTagA().getValueAsString();
|
||||
|
||||
if (valueA.matches(vA)) {
|
||||
result.add(k.getTagB().getValueAsString());
|
||||
}
|
||||
});
|
||||
if (valueA.matches(vA)) {
|
||||
result.add(k.getTagB().getValueAsString());
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find values for fieldB that are yield results when executing the query
|
||||
* "tag.field=tag.value and fieldB=???"
|
||||
*
|
||||
* @param dateRange the date range
|
||||
* @param tag the other tag
|
||||
* @param field the field we are searching values for
|
||||
* @return values for the field
|
||||
*/
|
||||
public SortedSet<String> find(final DateTimeRange dateRange, final Tag tag, final String field) {
|
||||
/**
|
||||
* Find values for fieldB that are yield results when executing the query
|
||||
* "tag.field=tag.value and fieldB=???"
|
||||
*
|
||||
* @param dateRange the date range
|
||||
* @param tag the other tag
|
||||
* @param field the field we are searching values for
|
||||
* @return values for the field
|
||||
*/
|
||||
public SortedSet<String> find(final DateTimeRange dateRange, final Tag tag, final String field) {
|
||||
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final int tagBKey = Tags.STRING_COMPRESSOR.put(field);
|
||||
final Tag tagB = new Tag(tagBKey, -1); // the value must be negative for the prefix search to work. See
|
||||
// EncoderTwoTags
|
||||
final TwoTags keyPrefix = new TwoTags(tag, tagB);
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final int tagBKey = Tags.STRING_COMPRESSOR.put(field);
|
||||
final Tag tagB = new Tag(tagBKey, -1); // the value must be negative for the prefix search to work. See
|
||||
// EncoderTwoTags
|
||||
final TwoTags keyPrefix = new TwoTags(tag, tagB);
|
||||
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
tagToTagIndex.visitValues(partitionIdSource, keyPrefix, (k, v) -> {
|
||||
result.add(k.getTagB().getValueAsString());
|
||||
});
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
tagToTagIndex.visitValues(partitionIdSource, keyPrefix, (k, v) -> {
|
||||
result.add(k.getTagB().getValueAsString());
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all values for the given field.
|
||||
*
|
||||
* @param dateRange the date range
|
||||
* @param field the field
|
||||
* @return the values
|
||||
*/
|
||||
public SortedSet<String> findAllValuesForField(final DateTimeRange dateRange, final String field) {
|
||||
/**
|
||||
* Find all values for the given field.
|
||||
*
|
||||
* @param dateRange the date range
|
||||
* @param field the field
|
||||
* @return the values
|
||||
*/
|
||||
public SortedSet<String> findAllValuesForField(final DateTimeRange dateRange, final String field) {
|
||||
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final int tagKey = Tags.STRING_COMPRESSOR.put(field);
|
||||
final Tag keyPrefix = new Tag(tagKey, -1); // the value must be negative for the prefix search to work. See
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final int tagKey = Tags.STRING_COMPRESSOR.put(field);
|
||||
final Tag keyPrefix = new Tag(tagKey, -1); // the value must be negative for the prefix search to work. See
|
||||
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
fieldToValueIndex.visitValues(partitionIdSource, keyPrefix, (k, v) -> {
|
||||
result.add(k.getValueAsString());
|
||||
});
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
fieldToValueIndex.visitValues(partitionIdSource, keyPrefix, (k, v) -> {
|
||||
result.add(k.getValueAsString());
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find values for {@code field} that will yield results for the query
|
||||
* "tag.field=tag.value and not field=???".
|
||||
* <p>
|
||||
*
|
||||
* @param dateRange the date range
|
||||
* @param tag the other tag
|
||||
* @param field the field we are searching values for
|
||||
* @return the values
|
||||
*/
|
||||
public SortedSet<String> findAllValuesNotForField(final DateTimeRange dateRange, final Tag tag,
|
||||
final String field) {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
/**
|
||||
* Find values for {@code field} that will yield results for the query
|
||||
* "tag.field=tag.value and not field=???".
|
||||
* <p>
|
||||
*
|
||||
* @param dateRange the date range
|
||||
* @param tag the other tag
|
||||
* @param field the field we are searching values for
|
||||
* @return the values
|
||||
*/
|
||||
public SortedSet<String> findAllValuesNotForField(final DateTimeRange dateRange, final Tag tag,
|
||||
final String field) {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
|
||||
final TwoTags keyPrefix = new TwoTags(field, tag.getKeyAsString(), null, null);
|
||||
final TwoTags keyPrefix = new TwoTags(field, tag.getKeyAsString(), null, null);
|
||||
|
||||
final int negatedValueA = tag.getValue();
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
tagToTagIndex.visitValues(partitionIdSource, keyPrefix, (k, v) -> {
|
||||
final int negatedValueA = tag.getValue();
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
tagToTagIndex.visitValues(partitionIdSource, keyPrefix, (k, v) -> {
|
||||
|
||||
final int valueA = k.getTagA().getValue();
|
||||
if (valueA != negatedValueA) {
|
||||
result.add(k.getTagB().getValueAsString());
|
||||
}
|
||||
});
|
||||
final int valueA = k.getTagA().getValue();
|
||||
if (valueA != negatedValueA) {
|
||||
result.add(k.getTagB().getValueAsString());
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public SortedSet<String> findAllFields(final DateTimeRange dateRange) {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
fieldIndex.visitValues(partitionIdSource, "", (k, v) -> {
|
||||
result.add(k);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
public SortedSet<String> findAllFields(final DateTimeRange dateRange) {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
fieldIndex.visitValues(partitionIdSource, "", (k, v) -> {
|
||||
result.add(k);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean hasField(final DateTimeRange dateRange, final String field) {
|
||||
final AtomicBoolean found = new AtomicBoolean(false);
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
fieldIndex.visitValues(partitionIdSource, "", (k, v) -> {
|
||||
if (k.equals(field)) {
|
||||
found.set(true);
|
||||
}
|
||||
});
|
||||
return found.get();
|
||||
}
|
||||
public boolean hasField(final DateTimeRange dateRange, final String field) {
|
||||
final AtomicBoolean found = new AtomicBoolean(false);
|
||||
final PartitionIdSource partitionIdSource = new DatePartitioner(dateRange);
|
||||
fieldIndex.visitValues(partitionIdSource, "", (k, v) -> {
|
||||
if (k.equals(field)) {
|
||||
found.set(true);
|
||||
}
|
||||
});
|
||||
return found.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,58 +7,58 @@ import org.lucares.pdb.map.PersistentMap.EncoderDecoder;
|
||||
import org.lucares.utils.byteencoder.VariableByteEncoder;
|
||||
|
||||
class TagEncoderDecoder implements EncoderDecoder<Tag> {
|
||||
@Override
|
||||
public byte[] encode(final Tag tag) {
|
||||
@Override
|
||||
public byte[] encode(final Tag tag) {
|
||||
|
||||
final LongList keyAndValueCompressed = new LongList(2);
|
||||
final LongList keyAndValueCompressed = new LongList(2);
|
||||
|
||||
final String key = tag.getKeyAsString();
|
||||
final byte[] result;
|
||||
if (!key.isEmpty()) {
|
||||
final Integer keyAsLong = Tags.STRING_COMPRESSOR.put(key);
|
||||
keyAndValueCompressed.add(keyAsLong);
|
||||
final String key = tag.getKeyAsString();
|
||||
final byte[] result;
|
||||
if (!key.isEmpty()) {
|
||||
final Integer keyAsLong = Tags.STRING_COMPRESSOR.put(key);
|
||||
keyAndValueCompressed.add(keyAsLong);
|
||||
|
||||
final String value = tag.getValueAsString();
|
||||
if (!value.isEmpty()) {
|
||||
final Integer valueAsLong = Tags.STRING_COMPRESSOR.put(value);
|
||||
keyAndValueCompressed.add(valueAsLong);
|
||||
}
|
||||
result = VariableByteEncoder.encode(keyAndValueCompressed);
|
||||
} else {
|
||||
result = new byte[0];
|
||||
}
|
||||
final String value = tag.getValueAsString();
|
||||
if (!value.isEmpty()) {
|
||||
final Integer valueAsLong = Tags.STRING_COMPRESSOR.put(value);
|
||||
keyAndValueCompressed.add(valueAsLong);
|
||||
}
|
||||
result = VariableByteEncoder.encode(keyAndValueCompressed);
|
||||
} else {
|
||||
result = new byte[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag decode(final byte[] bytes) {
|
||||
final LongList compressedStrings = VariableByteEncoder.decode(bytes);
|
||||
final Tag result;
|
||||
switch (compressedStrings.size()) {
|
||||
case 0:
|
||||
@Override
|
||||
public Tag decode(final byte[] bytes) {
|
||||
final LongList compressedStrings = VariableByteEncoder.decode(bytes);
|
||||
final Tag result;
|
||||
switch (compressedStrings.size()) {
|
||||
case 0:
|
||||
|
||||
result = new Tag("", "");
|
||||
break;
|
||||
case 1:
|
||||
final String k = Tags.STRING_COMPRESSOR.get((int) compressedStrings.get(0));
|
||||
result = new Tag(k, "");
|
||||
result = new Tag("", "");
|
||||
break;
|
||||
case 1:
|
||||
final String k = Tags.STRING_COMPRESSOR.get((int) compressedStrings.get(0));
|
||||
result = new Tag(k, "");
|
||||
|
||||
break;
|
||||
case 2:
|
||||
final String key = Tags.STRING_COMPRESSOR.get((int) compressedStrings.get(0));
|
||||
final String value = Tags.STRING_COMPRESSOR.get((int) compressedStrings.get(1));
|
||||
result = new Tag(key, value);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("too many values: " + compressedStrings);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
final String key = Tags.STRING_COMPRESSOR.get((int) compressedStrings.get(0));
|
||||
final String value = Tags.STRING_COMPRESSOR.get((int) compressedStrings.get(1));
|
||||
result = new Tag(key, value);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("too many values: " + compressedStrings);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] {0};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,18 @@ import org.lucares.pdb.api.Tags;
|
||||
import org.lucares.pdb.map.PersistentMap.EncoderDecoder;
|
||||
|
||||
class TagsEncoderDecoder implements EncoderDecoder<Tags> {
|
||||
@Override
|
||||
public byte[] encode(final Tags tags) {
|
||||
return tags.toBytes();
|
||||
}
|
||||
@Override
|
||||
public byte[] encode(final Tags tags) {
|
||||
return tags.toBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tags decode(final byte[] bytes) {
|
||||
return Tags.fromBytes(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] {};
|
||||
}
|
||||
@Override
|
||||
public Tags decode(final byte[] bytes) {
|
||||
return Tags.fromBytes(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEmptyValue() {
|
||||
return new byte[] {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,43 +9,43 @@ import java.util.TreeSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class CandidateGrouper {
|
||||
public SortedSet<String> group(final Collection<String> values, final String queryWithCaretMarker) {
|
||||
public SortedSet<String> group(final Collection<String> values, final String queryWithCaretMarker) {
|
||||
|
||||
final TreeSet<String> result = new TreeSet<>();
|
||||
final int numDotsInValue = countDotsInValue(queryWithCaretMarker);
|
||||
final TreeSet<String> result = new TreeSet<>();
|
||||
final int numDotsInValue = countDotsInValue(queryWithCaretMarker);
|
||||
|
||||
for (final String value : values) {
|
||||
// keep everything up to the (numDotsInValue+1)-th
|
||||
final String[] token = value.split(Pattern.quote("."));
|
||||
final List<String> tokenlist = new ArrayList<>(Arrays.asList(token));
|
||||
final List<String> prefix = tokenlist.subList(0, numDotsInValue + 1);
|
||||
String shortenedValue = String.join(".", prefix);
|
||||
if (tokenlist.size() > numDotsInValue + 1) {
|
||||
shortenedValue += ".";
|
||||
}
|
||||
result.add(shortenedValue);
|
||||
}
|
||||
for (final String value : values) {
|
||||
// keep everything up to the (numDotsInValue+1)-th
|
||||
final String[] token = value.split(Pattern.quote("."));
|
||||
final List<String> tokenlist = new ArrayList<>(Arrays.asList(token));
|
||||
final List<String> prefix = tokenlist.subList(0, numDotsInValue + 1);
|
||||
String shortenedValue = String.join(".", prefix);
|
||||
if (tokenlist.size() > numDotsInValue + 1) {
|
||||
shortenedValue += ".";
|
||||
}
|
||||
result.add(shortenedValue);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int countDotsInValue(final String queryWithCaretMarker) {
|
||||
private int countDotsInValue(final String queryWithCaretMarker) {
|
||||
|
||||
int count = 0;
|
||||
int index = queryWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER) - 1;
|
||||
final String delimiter = " (),=!";
|
||||
int count = 0;
|
||||
int index = queryWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER) - 1;
|
||||
final String delimiter = " (),=!";
|
||||
|
||||
while (index >= 0) {
|
||||
final char c = queryWithCaretMarker.charAt(index);
|
||||
if (delimiter.indexOf(c) >= 0) {
|
||||
break;
|
||||
}
|
||||
if (c == '.') {
|
||||
count++;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
while (index >= 0) {
|
||||
final char c = queryWithCaretMarker.charAt(index);
|
||||
if (delimiter.indexOf(c) >= 0) {
|
||||
break;
|
||||
}
|
||||
if (c == '.') {
|
||||
count++;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@ import org.antlr.v4.runtime.Recognizer;
|
||||
|
||||
public class ErrorListener extends BaseErrorListener {
|
||||
|
||||
@Override
|
||||
public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendingSymbol, final int line,
|
||||
final int charPositionInLine, final String msg, final RecognitionException e) {
|
||||
@Override
|
||||
public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendingSymbol, final int line,
|
||||
final int charPositionInLine, final String msg, final RecognitionException e) {
|
||||
|
||||
final int lineStart = line;
|
||||
final int startIndex = charPositionInLine;
|
||||
final int lineStop = line;
|
||||
final int stopIndex = charPositionInLine;
|
||||
throw new SyntaxException(msg, lineStart, startIndex, lineStop, stopIndex);
|
||||
}
|
||||
final int lineStart = line;
|
||||
final int startIndex = charPositionInLine;
|
||||
final int lineStop = line;
|
||||
final int stopIndex = charPositionInLine;
|
||||
throw new SyntaxException(msg, lineStart, startIndex, lineStop, stopIndex);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,179 +26,180 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ExpressionToDocIdVisitor extends ExpressionVisitor<PartitionLongList> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ExpressionToDocIdVisitor.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ExpressionToDocIdVisitor.class);
|
||||
|
||||
private final PartitionPersistentMap<Tag, Long, Long> keyToValueToDocId;
|
||||
private final PartitionDiskStore diskStorage;
|
||||
private final PartitionPersistentMap<Tag, Long, Long> keyToValueToDocId;
|
||||
private final PartitionDiskStore diskStorage;
|
||||
|
||||
private final DatePartitioner datePartitioner;
|
||||
private final DatePartitioner datePartitioner;
|
||||
|
||||
public ExpressionToDocIdVisitor(final DateTimeRange dateRange,
|
||||
final PartitionPersistentMap<Tag, Long, Long> keyToValueToDocsId, final PartitionDiskStore diskStorage) {
|
||||
this.datePartitioner = new DatePartitioner(dateRange);
|
||||
this.keyToValueToDocId = keyToValueToDocsId;
|
||||
this.diskStorage = diskStorage;
|
||||
}
|
||||
public ExpressionToDocIdVisitor(final DateTimeRange dateRange,
|
||||
final PartitionPersistentMap<Tag, Long, Long> keyToValueToDocsId, final PartitionDiskStore diskStorage) {
|
||||
this.datePartitioner = new DatePartitioner(dateRange);
|
||||
this.keyToValueToDocId = keyToValueToDocsId;
|
||||
this.diskStorage = diskStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartitionLongList visit(final And expression) {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
@Override
|
||||
public PartitionLongList visit(final And expression) {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
final PartitionLongList leftFiles = left.visit(this);
|
||||
final PartitionLongList rightFiles = right.visit(this);
|
||||
final PartitionLongList leftFiles = left.visit(this);
|
||||
final PartitionLongList rightFiles = right.visit(this);
|
||||
|
||||
final long start = System.nanoTime();
|
||||
final PartitionLongList result = PartitionLongList.intersection(leftFiles, rightFiles);
|
||||
LOGGER.trace("and: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
assert result.isSorted();
|
||||
final long start = System.nanoTime();
|
||||
final PartitionLongList result = PartitionLongList.intersection(leftFiles, rightFiles);
|
||||
LOGGER.trace("and: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
assert result.isSorted();
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartitionLongList visit(final Or expression) {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
@Override
|
||||
public PartitionLongList visit(final Or expression) {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
final PartitionLongList leftFiles = left.visit(this);
|
||||
final PartitionLongList rightFiles = right.visit(this);
|
||||
final long start = System.nanoTime();
|
||||
final PartitionLongList result = PartitionLongList.union(leftFiles, rightFiles);
|
||||
LOGGER.trace("or: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
assert result.isSorted();
|
||||
final PartitionLongList leftFiles = left.visit(this);
|
||||
final PartitionLongList rightFiles = right.visit(this);
|
||||
final long start = System.nanoTime();
|
||||
final PartitionLongList result = PartitionLongList.union(leftFiles, rightFiles);
|
||||
LOGGER.trace("or: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
assert result.isSorted();
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartitionLongList visit(final Not expression) {
|
||||
@Override
|
||||
public PartitionLongList visit(final Not expression) {
|
||||
|
||||
final Expression negatedExpression = expression.getExpression();
|
||||
final PartitionLongList docIdsToBeNegated = negatedExpression.visit(this);
|
||||
final long start = System.nanoTime();
|
||||
final Expression negatedExpression = expression.getExpression();
|
||||
final PartitionLongList docIdsToBeNegated = negatedExpression.visit(this);
|
||||
final long start = System.nanoTime();
|
||||
|
||||
final PartitionLongList result = getAllDocIds();
|
||||
result.removeAll(docIdsToBeNegated);
|
||||
final PartitionLongList result = getAllDocIds();
|
||||
result.removeAll(docIdsToBeNegated);
|
||||
|
||||
LOGGER.trace("not: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
LOGGER.trace("not: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartitionLongList visit(final Parentheses parentheses) {
|
||||
@Override
|
||||
public PartitionLongList visit(final Parentheses parentheses) {
|
||||
|
||||
throw new UnsupportedOperationException(
|
||||
"Parenthesis not supported. The correct order should come from the parser.");
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"Parenthesis not supported. The correct order should come from the parser.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartitionLongList visit(final Expression.MatchAll expression) {
|
||||
final long start = System.nanoTime();
|
||||
final PartitionLongList result = getAllDocIds();
|
||||
LOGGER.trace("matchAll: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
public PartitionLongList visit(final Expression.MatchAll expression) {
|
||||
final long start = System.nanoTime();
|
||||
final PartitionLongList result = getAllDocIds();
|
||||
LOGGER.trace("matchAll: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartitionLongList visit(final Expression.InExpression expression) {
|
||||
final long start = System.nanoTime();
|
||||
@Override
|
||||
public PartitionLongList visit(final Expression.InExpression expression) {
|
||||
final long start = System.nanoTime();
|
||||
|
||||
final String propertyName = expression.getProperty();
|
||||
final List<String> values = expression.getValues();
|
||||
final String propertyName = expression.getProperty();
|
||||
final List<String> values = expression.getValues();
|
||||
|
||||
PartitionLongList result = new PartitionLongList();
|
||||
PartitionLongList result = new PartitionLongList();
|
||||
|
||||
for (final String value : values) {
|
||||
for (final String value : values) {
|
||||
|
||||
final PartitionLongList docIds = filterByWildcard(propertyName, GloblikePattern.globlikeToRegex(value));
|
||||
result = PartitionLongList.union(result, docIds);
|
||||
}
|
||||
final PartitionLongList docIds = filterByWildcard(propertyName, GloblikePattern.globlikeToRegex(value));
|
||||
result = PartitionLongList.union(result, docIds);
|
||||
}
|
||||
|
||||
LOGGER.trace("in: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
return result;
|
||||
}
|
||||
LOGGER.trace("in: {} took {} ms results={}", expression, (System.nanoTime() - start) / 1_000_000.0,
|
||||
result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
private PartitionLongList getAllDocIds() {
|
||||
final PartitionLongList result = new PartitionLongList();
|
||||
final Set<ParititionId> availablePartitionIds = keyToValueToDocId.getAvailablePartitionIds(datePartitioner);
|
||||
for (final ParititionId partitionId : availablePartitionIds) {
|
||||
private PartitionLongList getAllDocIds() {
|
||||
final PartitionLongList result = new PartitionLongList();
|
||||
final Set<ParititionId> availablePartitionIds = keyToValueToDocId.getAvailablePartitionIds(datePartitioner);
|
||||
for (final ParititionId partitionId : availablePartitionIds) {
|
||||
|
||||
final Long blockOffset = keyToValueToDocId.getValue(partitionId, DataStore.TAG_ALL_DOCS);
|
||||
final Long blockOffset = keyToValueToDocId.getValue(partitionId, DataStore.TAG_ALL_DOCS);
|
||||
|
||||
if (blockOffset != null) {
|
||||
final LongStreamFile bsFile = diskStorage.streamExistingFile(blockOffset, partitionId);
|
||||
final LongList tmp = bsFile.asLongList();
|
||||
result.put(partitionId, tmp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (blockOffset != null) {
|
||||
final LongStreamFile bsFile = diskStorage.streamExistingFile(blockOffset, partitionId);
|
||||
final LongList tmp = bsFile.asLongList();
|
||||
result.put(partitionId, tmp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private PartitionLongList filterByWildcard(final String propertyName, final Pattern valuePattern) {
|
||||
final PartitionLongList result = new PartitionLongList();
|
||||
private PartitionLongList filterByWildcard(final String propertyName, final Pattern valuePattern) {
|
||||
final PartitionLongList result = new PartitionLongList();
|
||||
|
||||
final long start = System.nanoTime();
|
||||
final Set<ParititionId> availablePartitionIds = keyToValueToDocId.getAvailablePartitionIds(datePartitioner);
|
||||
for (final ParititionId partitionId : availablePartitionIds) {
|
||||
final List<LongList> docIdsForPartition = new ArrayList<>();
|
||||
keyToValueToDocId.visitValues(partitionId, new Tag(propertyName, ""), (tags, blockOffsetToDocIds) -> {
|
||||
if (valuePattern.matcher(tags.getValueAsString()).matches()) {
|
||||
try (final LongStreamFile bsFile = diskStorage.streamExistingFile(blockOffsetToDocIds, partitionId)) {
|
||||
final long start = System.nanoTime();
|
||||
final Set<ParititionId> availablePartitionIds = keyToValueToDocId.getAvailablePartitionIds(datePartitioner);
|
||||
for (final ParititionId partitionId : availablePartitionIds) {
|
||||
final List<LongList> docIdsForPartition = new ArrayList<>();
|
||||
keyToValueToDocId.visitValues(partitionId, new Tag(propertyName, ""), (tags, blockOffsetToDocIds) -> {
|
||||
if (valuePattern.matcher(tags.getValueAsString()).matches()) {
|
||||
try (final LongStreamFile bsFile = diskStorage.streamExistingFile(blockOffsetToDocIds,
|
||||
partitionId)) {
|
||||
|
||||
// We know that all LongLists coming from a BSFile are sorted, non-overlapping
|
||||
// and increasing, that means we can just concatenate them and get a sorted
|
||||
// list.
|
||||
final List<LongList> longLists = bsFile.streamOfLongLists().collect(Collectors.toList());
|
||||
final LongList concatenatedLists = concatenateLists(longLists);
|
||||
// We know that all LongLists coming from a BSFile are sorted, non-overlapping
|
||||
// and increasing, that means we can just concatenate them and get a sorted
|
||||
// list.
|
||||
final List<LongList> longLists = bsFile.streamOfLongLists().collect(Collectors.toList());
|
||||
final LongList concatenatedLists = concatenateLists(longLists);
|
||||
|
||||
Preconditions.checkTrue(concatenatedLists.isSorted(),
|
||||
"The LongLists containing document ids must be sorted, "
|
||||
+ "non-overlapping and increasing, so that the concatenation "
|
||||
+ "is sorted. This is guaranteed by the fact that document ids "
|
||||
+ "are generated in monotonically increasing order.");
|
||||
Preconditions.checkTrue(concatenatedLists.isSorted(),
|
||||
"The LongLists containing document ids must be sorted, "
|
||||
+ "non-overlapping and increasing, so that the concatenation "
|
||||
+ "is sorted. This is guaranteed by the fact that document ids "
|
||||
+ "are generated in monotonically increasing order.");
|
||||
|
||||
docIdsForPartition.add(concatenatedLists);
|
||||
}
|
||||
}
|
||||
});
|
||||
docIdsForPartition.add(concatenatedLists);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final LongList mergedDocsIdsForPartition = merge(docIdsForPartition);
|
||||
result.put(partitionId, mergedDocsIdsForPartition);
|
||||
}
|
||||
final LongList mergedDocsIdsForPartition = merge(docIdsForPartition);
|
||||
result.put(partitionId, mergedDocsIdsForPartition);
|
||||
}
|
||||
|
||||
LOGGER.trace("filterByWildcard: for key {} took {}ms", propertyName, (System.nanoTime() - start) / 1_000_000.0);
|
||||
LOGGER.trace("filterByWildcard: for key {} took {}ms", propertyName, (System.nanoTime() - start) / 1_000_000.0);
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private LongList merge(final Collection<LongList> lists) {
|
||||
private LongList merge(final Collection<LongList> lists) {
|
||||
|
||||
LongList result = new LongList();
|
||||
LongList result = new LongList();
|
||||
|
||||
for (final LongList list : lists) {
|
||||
result = LongList.union(result, list);
|
||||
}
|
||||
for (final LongList list : lists) {
|
||||
result = LongList.union(result, list);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static LongList concatenateLists(final Collection<LongList> lists) {
|
||||
private static LongList concatenateLists(final Collection<LongList> lists) {
|
||||
|
||||
final int totalSize = lists.stream().mapToInt(LongList::size).sum();
|
||||
final LongList result = new LongList(totalSize);
|
||||
final int totalSize = lists.stream().mapToInt(LongList::size).sum();
|
||||
final LongList result = new LongList(totalSize);
|
||||
|
||||
for (final LongList list : lists) {
|
||||
result.addAll(list);
|
||||
}
|
||||
for (final LongList list : lists) {
|
||||
result.addAll(list);
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
package org.lucares.pdb.datastore.lang;
|
||||
|
||||
public abstract class ExpressionVisitor<T> {
|
||||
public T visit(final Expression.And expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.And expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.Or expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.Or expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.Not expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.Not expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.Property expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.Property expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.Terminal expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.Terminal expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.MatchAll expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.MatchAll expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.InExpression expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.InExpression expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.Parentheses parentheses) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.Parentheses parentheses) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.AndCaretExpression expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.AndCaretExpression expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.AndNotCaretExpression expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.AndNotCaretExpression expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public T visit(final Expression.CaretAndExpression expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public T visit(final Expression.CaretAndExpression expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,278 +22,278 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<String>> {
|
||||
|
||||
private static final Logger METRIC_AND_CARET_LOGGER = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.queryCompletion.expressionEvaluation.andCaret");
|
||||
private static final Logger METRIC_AND_CARET_LOGGER = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.queryCompletion.expressionEvaluation.andCaret");
|
||||
|
||||
private static final Logger METRIC_LOGGER = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.queryCompletion.expressionEvaluation");
|
||||
private static final Logger METRIC_LOGGER = LoggerFactory
|
||||
.getLogger("org.lucares.metrics.queryCompletion.expressionEvaluation");
|
||||
|
||||
private static final class AndCaretExpressionVisitor extends ExpressionVisitor<SortedSet<String>> {
|
||||
private final QueryCompletionIndex index;
|
||||
private final String field;
|
||||
private final DateTimeRange dateTimeRange;
|
||||
private static final class AndCaretExpressionVisitor extends ExpressionVisitor<SortedSet<String>> {
|
||||
private final QueryCompletionIndex index;
|
||||
private final String field;
|
||||
private final DateTimeRange dateTimeRange;
|
||||
|
||||
public AndCaretExpressionVisitor(final DateTimeRange dateTimeRange,
|
||||
final QueryCompletionIndex queryCompletionIndex, final String field) {
|
||||
this.dateTimeRange = dateTimeRange;
|
||||
index = queryCompletionIndex;
|
||||
this.field = field;
|
||||
}
|
||||
public AndCaretExpressionVisitor(final DateTimeRange dateTimeRange,
|
||||
final QueryCompletionIndex queryCompletionIndex, final String field) {
|
||||
this.dateTimeRange = dateTimeRange;
|
||||
index = queryCompletionIndex;
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final Property property) {
|
||||
final long start = System.nanoTime();
|
||||
final SortedSet<String> result;
|
||||
@Override
|
||||
public SortedSet<String> visit(final Property property) {
|
||||
final long start = System.nanoTime();
|
||||
final SortedSet<String> result;
|
||||
|
||||
final String fieldA = property.getField();
|
||||
final String valueA = property.getValue().getValue();
|
||||
final String fieldA = property.getField();
|
||||
final String valueA = property.getValue().getValue();
|
||||
|
||||
final boolean hasField = index.hasField(dateTimeRange, fieldA);
|
||||
if (hasField) {
|
||||
final boolean hasField = index.hasField(dateTimeRange, fieldA);
|
||||
if (hasField) {
|
||||
|
||||
final SortedSet<String> allValuesForField = index.findAllValuesForField(dateTimeRange, fieldA);
|
||||
final SortedSet<String> valuesA = GloblikePattern.filterValues(allValuesForField, valueA, TreeSet::new);
|
||||
final SortedSet<String> allValuesForField = index.findAllValuesForField(dateTimeRange, fieldA);
|
||||
final SortedSet<String> valuesA = GloblikePattern.filterValues(allValuesForField, valueA, TreeSet::new);
|
||||
|
||||
final double valueInFieldAMatchPercentage = valuesA.size() / (double) allValuesForField.size();
|
||||
final boolean useMultiFetch = valuesA.size() <= 1 || valueInFieldAMatchPercentage < 0.5; // 50% was
|
||||
// chosen
|
||||
// arbitrarily
|
||||
if (useMultiFetch) {
|
||||
result = new TreeSet<>();
|
||||
final double valueInFieldAMatchPercentage = valuesA.size() / (double) allValuesForField.size();
|
||||
final boolean useMultiFetch = valuesA.size() <= 1 || valueInFieldAMatchPercentage < 0.5; // 50% was
|
||||
// chosen
|
||||
// arbitrarily
|
||||
if (useMultiFetch) {
|
||||
result = new TreeSet<>();
|
||||
|
||||
for (final String v : valuesA) {
|
||||
final Tag tagA = new Tag(fieldA, v);
|
||||
final SortedSet<String> tmp = index.find(dateTimeRange, tagA, field);
|
||||
result.addAll(tmp);
|
||||
}
|
||||
} else {
|
||||
result = index.find(dateTimeRange, fieldA, new GlobMatcher(valueA), field);
|
||||
}
|
||||
for (final String v : valuesA) {
|
||||
final Tag tagA = new Tag(fieldA, v);
|
||||
final SortedSet<String> tmp = index.find(dateTimeRange, tagA, field);
|
||||
result.addAll(tmp);
|
||||
}
|
||||
} else {
|
||||
result = index.find(dateTimeRange, fieldA, new GlobMatcher(valueA), field);
|
||||
}
|
||||
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {} and {}=???: {}ms matches in fieldA {} ({}%)",
|
||||
useMultiFetch ? "multi-fetch" : "single-fetch", property, field,
|
||||
(System.nanoTime() - start) / 1_000_000.0, valuesA.size(), valueInFieldAMatchPercentage * 100);
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {} and {}=???: {}ms matches in fieldA {} ({}%)",
|
||||
useMultiFetch ? "multi-fetch" : "single-fetch", property, field,
|
||||
(System.nanoTime() - start) / 1_000_000.0, valuesA.size(), valueInFieldAMatchPercentage * 100);
|
||||
|
||||
} else {
|
||||
result = new TreeSet<>();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
result = new TreeSet<>();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final InExpression expression) {
|
||||
final long start = System.nanoTime();
|
||||
final SortedSet<String> result;
|
||||
final String fieldA = expression.getProperty();
|
||||
final List<String> values = expression.getValues();
|
||||
@Override
|
||||
public SortedSet<String> visit(final InExpression expression) {
|
||||
final long start = System.nanoTime();
|
||||
final SortedSet<String> result;
|
||||
final String fieldA = expression.getProperty();
|
||||
final List<String> values = expression.getValues();
|
||||
|
||||
result = index.find(dateTimeRange, fieldA, new GlobMatcher(values), field);
|
||||
result = index.find(dateTimeRange, fieldA, new GlobMatcher(values), field);
|
||||
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final And expression) {
|
||||
final long start = System.nanoTime();
|
||||
try {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
@Override
|
||||
public SortedSet<String> visit(final And expression) {
|
||||
final long start = System.nanoTime();
|
||||
try {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
if (left instanceof Property && right instanceof Not) {
|
||||
final Property leftProperty = (Property) left;
|
||||
if (left instanceof Property && right instanceof Not) {
|
||||
final Property leftProperty = (Property) left;
|
||||
|
||||
final SortedSet<String> allValuesForField = leftProperty.visit(this);
|
||||
final SortedSet<String> allValuesForField = leftProperty.visit(this);
|
||||
|
||||
final Expression rightInnerExpression = ((Not) right).getExpression();
|
||||
final SortedSet<String> rightResult = rightInnerExpression.visit(this);
|
||||
final Expression rightInnerExpression = ((Not) right).getExpression();
|
||||
final SortedSet<String> rightResult = rightInnerExpression.visit(this);
|
||||
|
||||
return CollectionUtils.removeAll(allValuesForField, rightResult, TreeSet::new);
|
||||
return CollectionUtils.removeAll(allValuesForField, rightResult, TreeSet::new);
|
||||
|
||||
} else {
|
||||
} else {
|
||||
|
||||
final SortedSet<String> result = left.visit(this);
|
||||
final SortedSet<String> rightResult = right.visit(this);
|
||||
final SortedSet<String> result = left.visit(this);
|
||||
final SortedSet<String> rightResult = right.visit(this);
|
||||
|
||||
result.retainAll(rightResult);
|
||||
result.retainAll(rightResult);
|
||||
|
||||
return result;
|
||||
}
|
||||
} finally {
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} finally {
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final Or expression) {
|
||||
@Override
|
||||
public SortedSet<String> visit(final Or expression) {
|
||||
|
||||
final long start = System.nanoTime();
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
final long start = System.nanoTime();
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
final SortedSet<String> result = left.visit(this);
|
||||
final SortedSet<String> rightResult = right.visit(this);
|
||||
final SortedSet<String> result = left.visit(this);
|
||||
final SortedSet<String> rightResult = right.visit(this);
|
||||
|
||||
result.addAll(rightResult);
|
||||
result.addAll(rightResult);
|
||||
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final Not expression) {
|
||||
@Override
|
||||
public SortedSet<String> visit(final Not expression) {
|
||||
|
||||
final long start = System.nanoTime();
|
||||
if (!(expression.getExpression() instanceof Property)) {
|
||||
throw new UnsupportedOperationException("NOT expressions like '" + expression
|
||||
+ "' are not supported. Only 'NOT property=value' expressions are supported.");
|
||||
}
|
||||
final long start = System.nanoTime();
|
||||
if (!(expression.getExpression() instanceof Property)) {
|
||||
throw new UnsupportedOperationException("NOT expressions like '" + expression
|
||||
+ "' are not supported. Only 'NOT property=value' expressions are supported.");
|
||||
}
|
||||
|
||||
final Property property = (Property) expression.getExpression();
|
||||
final Tag tag = new Tag(property.getField(), property.getValueAsString());
|
||||
final Property property = (Property) expression.getExpression();
|
||||
final Tag tag = new Tag(property.getField(), property.getValueAsString());
|
||||
|
||||
final SortedSet<String> valuesNotForField = index.findAllValuesNotForField(dateTimeRange, tag, field);
|
||||
final SortedSet<String> valuesForField = index.find(dateTimeRange, tag, field);
|
||||
final SortedSet<String> valuesOnlyAvailableInField = CollectionUtils.removeAll(valuesForField,
|
||||
valuesNotForField, TreeSet::new);
|
||||
final SortedSet<String> valuesNotForField = index.findAllValuesNotForField(dateTimeRange, tag, field);
|
||||
final SortedSet<String> valuesForField = index.find(dateTimeRange, tag, field);
|
||||
final SortedSet<String> valuesOnlyAvailableInField = CollectionUtils.removeAll(valuesForField,
|
||||
valuesNotForField, TreeSet::new);
|
||||
|
||||
final SortedSet<String> result = CollectionUtils.removeAll(valuesNotForField, valuesOnlyAvailableInField,
|
||||
TreeSet::new);
|
||||
final SortedSet<String> result = CollectionUtils.removeAll(valuesNotForField, valuesOnlyAvailableInField,
|
||||
TreeSet::new);
|
||||
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private final QueryCompletionIndex queryCompletionIndex;
|
||||
|
||||
private final DateTimeRange dateRange;
|
||||
|
||||
public FindValuesForQueryCompletion(final DateTimeRange dateRange,
|
||||
final QueryCompletionIndex queryCompletionIndex) {
|
||||
this.dateRange = dateRange;
|
||||
this.queryCompletionIndex = queryCompletionIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final Property property) {
|
||||
|
||||
final long start = System.nanoTime();
|
||||
final String field = property.getField();
|
||||
final String value = property.getValue().getValue();
|
||||
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(dateRange, field);
|
||||
|
||||
final String valuePrefix;
|
||||
|
||||
if (value.indexOf(NewProposerParser.CARET_MARKER) >= 0) {
|
||||
valuePrefix = value.substring(0, value.indexOf(NewProposerParser.CARET_MARKER));
|
||||
} else {
|
||||
valuePrefix = value;
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
final TreeSet<String> result = GloblikePattern.filterValues(allValuesForField, valuePrefix, TreeSet::new);
|
||||
METRIC_LOGGER.debug("{}: {}ms", property, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
private final QueryCompletionIndex queryCompletionIndex;
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final AndCaretExpression expression) {
|
||||
private final DateTimeRange dateRange;
|
||||
|
||||
final long start = System.nanoTime();
|
||||
final Property caretExpression = expression.getCaretExpression();
|
||||
final String field = caretExpression.getField();
|
||||
final String valueWithCaretMarker = caretExpression.getValue().getValue();
|
||||
final String valuePrefix = valueWithCaretMarker.substring(0,
|
||||
valueWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER));
|
||||
public FindValuesForQueryCompletion(final DateTimeRange dateRange,
|
||||
final QueryCompletionIndex queryCompletionIndex) {
|
||||
this.dateRange = dateRange;
|
||||
this.queryCompletionIndex = queryCompletionIndex;
|
||||
}
|
||||
|
||||
final Expression rightHandExpression = expression.getExpression();
|
||||
@Override
|
||||
public SortedSet<String> visit(final Property property) {
|
||||
|
||||
final SortedSet<String> candidateValues = rightHandExpression
|
||||
.visit(new AndCaretExpressionVisitor(dateRange, queryCompletionIndex, field));
|
||||
final long start = System.nanoTime();
|
||||
final String field = property.getField();
|
||||
final String value = property.getValue().getValue();
|
||||
|
||||
final TreeSet<String> result = GloblikePattern.filterValues(candidateValues, valuePrefix, TreeSet::new);
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(dateRange, field);
|
||||
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
final String valuePrefix;
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final AndNotCaretExpression expression) {
|
||||
if (value.indexOf(NewProposerParser.CARET_MARKER) >= 0) {
|
||||
valuePrefix = value.substring(0, value.indexOf(NewProposerParser.CARET_MARKER));
|
||||
} else {
|
||||
valuePrefix = value;
|
||||
}
|
||||
|
||||
final long start = System.nanoTime();
|
||||
final Property caretExpression = expression.getCaretExpression();
|
||||
final String field = caretExpression.getField();
|
||||
final String valueWithCaretMarker = caretExpression.getValue().getValue();
|
||||
final String valuePattern = valueWithCaretMarker.substring(0,
|
||||
valueWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER));
|
||||
final TreeSet<String> result = GloblikePattern.filterValues(allValuesForField, valuePrefix, TreeSet::new);
|
||||
METRIC_LOGGER.debug("{}: {}ms", property, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(dateRange,
|
||||
caretExpression.getField());
|
||||
final SortedSet<String> valuesForFieldMatchingCaretExpression = GloblikePattern.filterValues(allValuesForField,
|
||||
valuePattern, TreeSet::new);
|
||||
@Override
|
||||
public SortedSet<String> visit(final AndCaretExpression expression) {
|
||||
|
||||
final Expression rightHandExpression = expression.getExpression();
|
||||
final long start = System.nanoTime();
|
||||
final Property caretExpression = expression.getCaretExpression();
|
||||
final String field = caretExpression.getField();
|
||||
final String valueWithCaretMarker = caretExpression.getValue().getValue();
|
||||
final String valuePrefix = valueWithCaretMarker.substring(0,
|
||||
valueWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER));
|
||||
|
||||
final SortedSet<String> rightHandValues = rightHandExpression
|
||||
.visit(new AndCaretExpressionVisitor(dateRange, queryCompletionIndex, field));
|
||||
final Expression rightHandExpression = expression.getExpression();
|
||||
|
||||
if (rightHandValues.size() == 1) {
|
||||
// there is only one alternative and that one must not be chosen
|
||||
return Collections.emptySortedSet();
|
||||
}
|
||||
final SortedSet<String> result = CollectionUtils.retainAll(rightHandValues,
|
||||
valuesForFieldMatchingCaretExpression, TreeSet::new);
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
final SortedSet<String> candidateValues = rightHandExpression
|
||||
.visit(new AndCaretExpressionVisitor(dateRange, queryCompletionIndex, field));
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final Not expression) {
|
||||
final TreeSet<String> result = GloblikePattern.filterValues(candidateValues, valuePrefix, TreeSet::new);
|
||||
|
||||
final String field;
|
||||
final Expression innerExpression = expression.getExpression();
|
||||
if (innerExpression instanceof Property) {
|
||||
final long start = System.nanoTime();
|
||||
field = ((Property) innerExpression).getField();
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(dateRange, field);
|
||||
final String valueWithCaretMarker = ((Property) innerExpression).getValue().getValue();
|
||||
final String valuePrefix = valueWithCaretMarker.substring(0,
|
||||
valueWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER));
|
||||
final TreeSet<String> result = GloblikePattern.filterValues(allValuesForField, valuePrefix + "*",
|
||||
TreeSet::new);
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final Or expression) {
|
||||
final long start = System.nanoTime();
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
@Override
|
||||
public SortedSet<String> visit(final AndNotCaretExpression expression) {
|
||||
|
||||
final SortedSet<String> result = left.visit(this);
|
||||
final SortedSet<String> rightResult = right.visit(this);
|
||||
final long start = System.nanoTime();
|
||||
final Property caretExpression = expression.getCaretExpression();
|
||||
final String field = caretExpression.getField();
|
||||
final String valueWithCaretMarker = caretExpression.getValue().getValue();
|
||||
final String valuePattern = valueWithCaretMarker.substring(0,
|
||||
valueWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER));
|
||||
|
||||
result.addAll(rightResult);
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(dateRange,
|
||||
caretExpression.getField());
|
||||
final SortedSet<String> valuesForFieldMatchingCaretExpression = GloblikePattern.filterValues(allValuesForField,
|
||||
valuePattern, TreeSet::new);
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final And expression) {
|
||||
final long start = System.nanoTime();
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
final Expression rightHandExpression = expression.getExpression();
|
||||
|
||||
final SortedSet<String> result = left.visit(this);
|
||||
final SortedSet<String> rightResult = right.visit(this);
|
||||
final SortedSet<String> rightHandValues = rightHandExpression
|
||||
.visit(new AndCaretExpressionVisitor(dateRange, queryCompletionIndex, field));
|
||||
|
||||
result.retainAll(rightResult);
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
if (rightHandValues.size() == 1) {
|
||||
// there is only one alternative and that one must not be chosen
|
||||
return Collections.emptySortedSet();
|
||||
}
|
||||
final SortedSet<String> result = CollectionUtils.retainAll(rightHandValues,
|
||||
valuesForFieldMatchingCaretExpression, TreeSet::new);
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final Not expression) {
|
||||
|
||||
final String field;
|
||||
final Expression innerExpression = expression.getExpression();
|
||||
if (innerExpression instanceof Property) {
|
||||
final long start = System.nanoTime();
|
||||
field = ((Property) innerExpression).getField();
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(dateRange, field);
|
||||
final String valueWithCaretMarker = ((Property) innerExpression).getValue().getValue();
|
||||
final String valuePrefix = valueWithCaretMarker.substring(0,
|
||||
valueWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER));
|
||||
final TreeSet<String> result = GloblikePattern.filterValues(allValuesForField, valuePrefix + "*",
|
||||
TreeSet::new);
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final Or expression) {
|
||||
final long start = System.nanoTime();
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
final SortedSet<String> result = left.visit(this);
|
||||
final SortedSet<String> rightResult = right.visit(this);
|
||||
|
||||
result.addAll(rightResult);
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> visit(final And expression) {
|
||||
final long start = System.nanoTime();
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
final SortedSet<String> result = left.visit(this);
|
||||
final SortedSet<String> rightResult = right.visit(this);
|
||||
|
||||
result.retainAll(rightResult);
|
||||
METRIC_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,70 +12,70 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
public class GloblikePattern {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(GloblikePattern.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(GloblikePattern.class);
|
||||
|
||||
enum FilterMode {
|
||||
KEEP_EQUAL
|
||||
}
|
||||
enum FilterMode {
|
||||
KEEP_EQUAL
|
||||
}
|
||||
|
||||
public static Pattern globlikeToRegex(final String globlike) {
|
||||
public static Pattern globlikeToRegex(final String globlike) {
|
||||
|
||||
final String valueRegex = "^" + globlikeToPattern(globlike);
|
||||
final String valueRegex = "^" + globlikeToPattern(globlike);
|
||||
|
||||
LOGGER.trace(">{}< -> >{}<", globlike, valueRegex);
|
||||
LOGGER.trace(">{}< -> >{}<", globlike, valueRegex);
|
||||
|
||||
return Pattern.compile(valueRegex);
|
||||
}
|
||||
return Pattern.compile(valueRegex);
|
||||
}
|
||||
|
||||
public static Pattern globlikeToRegex(final Iterable<String> globlikes) {
|
||||
public static Pattern globlikeToRegex(final Iterable<String> globlikes) {
|
||||
|
||||
final List<String> regex = new ArrayList<>();
|
||||
final List<String> regex = new ArrayList<>();
|
||||
|
||||
for (final String globlike : globlikes) {
|
||||
regex.add(globlikeToPattern(globlike));
|
||||
}
|
||||
final StringBuilder fullRegex = new StringBuilder("^(");
|
||||
fullRegex.append(String.join("|", regex));
|
||||
fullRegex.append(")");
|
||||
for (final String globlike : globlikes) {
|
||||
regex.add(globlikeToPattern(globlike));
|
||||
}
|
||||
final StringBuilder fullRegex = new StringBuilder("^(");
|
||||
fullRegex.append(String.join("|", regex));
|
||||
fullRegex.append(")");
|
||||
|
||||
LOGGER.trace(">{}< -> >{}<", globlikes, fullRegex);
|
||||
LOGGER.trace(">{}< -> >{}<", globlikes, fullRegex);
|
||||
|
||||
return Pattern.compile(fullRegex.toString());
|
||||
}
|
||||
return Pattern.compile(fullRegex.toString());
|
||||
}
|
||||
|
||||
private static String globlikeToPattern(final String globlike) {
|
||||
// a character that cannot be in the globPattern
|
||||
final String dotPlaceholder = "\ue003"; // fourth character in the private use area
|
||||
private static String globlikeToPattern(final String globlike) {
|
||||
// a character that cannot be in the globPattern
|
||||
final String dotPlaceholder = "\ue003"; // fourth character in the private use area
|
||||
|
||||
final String valueRegex = globlike//
|
||||
.replace("-", Pattern.quote("-"))//
|
||||
.replace(".", dotPlaceholder)//
|
||||
.replace("*", ".*")//
|
||||
.replace(dotPlaceholder, ".*\\.")//
|
||||
.replaceAll("([A-Z])", "[a-z]*$1");
|
||||
return valueRegex;
|
||||
}
|
||||
final String valueRegex = globlike//
|
||||
.replace("-", Pattern.quote("-"))//
|
||||
.replace(".", dotPlaceholder)//
|
||||
.replace("*", ".*")//
|
||||
.replace(dotPlaceholder, ".*\\.")//
|
||||
.replaceAll("([A-Z])", "[a-z]*$1");
|
||||
return valueRegex;
|
||||
}
|
||||
|
||||
public static <T extends Collection<String>> T filterValues(final Collection<String> availableValues,
|
||||
final String valuePattern, final Supplier<T> generator) {
|
||||
final T result = generator.get();
|
||||
public static <T extends Collection<String>> T filterValues(final Collection<String> availableValues,
|
||||
final String valuePattern, final Supplier<T> generator) {
|
||||
final T result = generator.get();
|
||||
|
||||
return filterValues(result, availableValues, valuePattern);
|
||||
}
|
||||
return filterValues(result, availableValues, valuePattern);
|
||||
}
|
||||
|
||||
public static <T extends Collection<String>> T filterValues(final T result,
|
||||
final Collection<String> availableValues, final String valuePattern) {
|
||||
public static <T extends Collection<String>> T filterValues(final T result,
|
||||
final Collection<String> availableValues, final String valuePattern) {
|
||||
|
||||
final Pattern pattern = GloblikePattern.globlikeToRegex(valuePattern);
|
||||
final Pattern pattern = GloblikePattern.globlikeToRegex(valuePattern);
|
||||
|
||||
for (final String value : availableValues) {
|
||||
final Matcher matcher = pattern.matcher(value);
|
||||
if (matcher.find()) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
for (final String value : availableValues) {
|
||||
final Matcher matcher = pattern.matcher(value);
|
||||
if (matcher.find()) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,66 +14,66 @@ import org.lucares.pdb.datastore.lang.Expression.Property;
|
||||
* as base class for visitors that modify expressions.
|
||||
*/
|
||||
public abstract class IdentityExpressionVisitor extends ExpressionVisitor<Expression> {
|
||||
@Override
|
||||
public Expression visit(final And expression) {
|
||||
@Override
|
||||
public Expression visit(final And expression) {
|
||||
|
||||
final Expression left = expression.getLeft().visit(this);
|
||||
final Expression right = expression.getRight().visit(this);
|
||||
final Expression left = expression.getLeft().visit(this);
|
||||
final Expression right = expression.getRight().visit(this);
|
||||
|
||||
return new And(left, right);
|
||||
}
|
||||
return new And(left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final Or expression) {
|
||||
final Expression left = expression.getLeft().visit(this);
|
||||
final Expression right = expression.getRight().visit(this);
|
||||
@Override
|
||||
public Expression visit(final Or expression) {
|
||||
final Expression left = expression.getLeft().visit(this);
|
||||
final Expression right = expression.getRight().visit(this);
|
||||
|
||||
return new Or(left, right);
|
||||
}
|
||||
return new Or(left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final Not expression) {
|
||||
return new Not(expression.getExpression().visit(this));
|
||||
}
|
||||
@Override
|
||||
public Expression visit(final Not expression) {
|
||||
return new Not(expression.getExpression().visit(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final Property expression) {
|
||||
return expression;
|
||||
}
|
||||
@Override
|
||||
public Expression visit(final Property expression) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final Expression.Terminal expression) {
|
||||
return expression;
|
||||
}
|
||||
@Override
|
||||
public Expression visit(final Expression.Terminal expression) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final Expression.MatchAll expression) {
|
||||
return expression;
|
||||
}
|
||||
@Override
|
||||
public Expression visit(final Expression.MatchAll expression) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final Expression.InExpression expression) {
|
||||
return expression;
|
||||
}
|
||||
@Override
|
||||
public Expression visit(final Expression.InExpression expression) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final Parentheses parentheses) {
|
||||
return new Parentheses(parentheses.getExpression().visit(this));
|
||||
}
|
||||
@Override
|
||||
public Expression visit(final Parentheses parentheses) {
|
||||
return new Parentheses(parentheses.getExpression().visit(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final AndCaretExpression expression) {
|
||||
return expression;
|
||||
}
|
||||
@Override
|
||||
public Expression visit(final AndCaretExpression expression) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final AndNotCaretExpression expression) {
|
||||
return expression;
|
||||
}
|
||||
@Override
|
||||
public Expression visit(final AndNotCaretExpression expression) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visit(final CaretAndExpression expression) {
|
||||
return expression;
|
||||
}
|
||||
@Override
|
||||
public Expression visit(final CaretAndExpression expression) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,203 +21,203 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
public class NewProposerParser implements QueryConstants {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NewProposerParser.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NewProposerParser.class);
|
||||
|
||||
private final static Logger METRICS_LOGGER_PROPOSE = LoggerFactory.getLogger("org.lucares.metrics.propose");
|
||||
private final static Logger METRICS_LOGGER_PROPOSE = LoggerFactory.getLogger("org.lucares.metrics.propose");
|
||||
|
||||
/*
|
||||
* Regex matching a java identifier without a caret marker. We define it as a
|
||||
* blacklist, because this is easer. The regex is only used <em>after</em> the
|
||||
* query has already been validated with the proper grammar.
|
||||
*/
|
||||
private static final String REGEX_IDENTIFIER = "[^\\s,!\\(\\)=" + CARET_MARKER + "]*";
|
||||
/*
|
||||
* Regex matching a java identifier without a caret marker. We define it as a
|
||||
* blacklist, because this is easer. The regex is only used <em>after</em> the
|
||||
* query has already been validated with the proper grammar.
|
||||
*/
|
||||
private static final String REGEX_IDENTIFIER = "[^\\s,!\\(\\)=" + CARET_MARKER + "]*";
|
||||
|
||||
private final QueryCompletionIndex queryCompletionIndex;
|
||||
private final QueryCompletionIndex queryCompletionIndex;
|
||||
|
||||
public NewProposerParser(final QueryCompletionIndex queryCompletionIndex) {
|
||||
this.queryCompletionIndex = queryCompletionIndex;
|
||||
}
|
||||
public NewProposerParser(final QueryCompletionIndex queryCompletionIndex) {
|
||||
this.queryCompletionIndex = queryCompletionIndex;
|
||||
}
|
||||
|
||||
public List<Proposal> propose(final QueryWithCaretMarker query) {
|
||||
final long start = System.nanoTime();
|
||||
List<Proposal> proposals;
|
||||
if (StringUtils.isBlank(query.getQuery())) {
|
||||
proposals = proposeForAllKeys(query.getDateRange());
|
||||
} else {
|
||||
public List<Proposal> propose(final QueryWithCaretMarker query) {
|
||||
final long start = System.nanoTime();
|
||||
List<Proposal> proposals;
|
||||
if (StringUtils.isBlank(query.getQuery())) {
|
||||
proposals = proposeForAllKeys(query.getDateRange());
|
||||
} else {
|
||||
|
||||
final List<Proposal> foundProposals = proposalsForValues(query);
|
||||
if (foundProposals.isEmpty()) {
|
||||
proposals = proposalsForNonValues(query);
|
||||
} else {
|
||||
proposals = foundProposals;
|
||||
}
|
||||
}
|
||||
final List<Proposal> nonEmptyProposals = CollectionUtils.filter(proposals, p -> p.hasResults());
|
||||
final List<Proposal> foundProposals = proposalsForValues(query);
|
||||
if (foundProposals.isEmpty()) {
|
||||
proposals = proposalsForNonValues(query);
|
||||
} else {
|
||||
proposals = foundProposals;
|
||||
}
|
||||
}
|
||||
final List<Proposal> nonEmptyProposals = CollectionUtils.filter(proposals, p -> p.hasResults());
|
||||
|
||||
METRICS_LOGGER_PROPOSE.debug("compute proposals took {}ms for query '{}' ",
|
||||
(System.nanoTime() - start) / 1_000_000.0, query);
|
||||
METRICS_LOGGER_PROPOSE.debug("compute proposals took {}ms for query '{}' ",
|
||||
(System.nanoTime() - start) / 1_000_000.0, query);
|
||||
|
||||
return nonEmptyProposals;
|
||||
}
|
||||
return nonEmptyProposals;
|
||||
}
|
||||
|
||||
private List<Proposal> proposalsForNonValues(final QueryWithCaretMarker query) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
private List<Proposal> proposalsForNonValues(final QueryWithCaretMarker query) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
|
||||
/*
|
||||
* This method is called when the query could not be parsed. It is likely that
|
||||
* the next word is either a field or an operator. But is is also possible that
|
||||
* the next word is a field-value, because the syntax error might be at another
|
||||
* location in the query (not at the caret position).
|
||||
*/
|
||||
/*
|
||||
* This method is called when the query could not be parsed. It is likely that
|
||||
* the next word is either a field or an operator. But is is also possible that
|
||||
* the next word is a field-value, because the syntax error might be at another
|
||||
* location in the query (not at the caret position).
|
||||
*/
|
||||
|
||||
final List<String> tokens = QueryLanguage.getTokens(query.getQueryWithCaretMarker());
|
||||
final int indexTokenWithCaret = CollectionUtils.indexOf(tokens, t -> t.contains(CARET_MARKER));
|
||||
final List<String> tokens = QueryLanguage.getTokens(query.getQueryWithCaretMarker());
|
||||
final int indexTokenWithCaret = CollectionUtils.indexOf(tokens, t -> t.contains(CARET_MARKER));
|
||||
|
||||
if (indexTokenWithCaret > 0) {
|
||||
final String previousToken = tokens.get(indexTokenWithCaret - 1);
|
||||
switch (previousToken) {
|
||||
case "(":
|
||||
case "and":
|
||||
case "or":
|
||||
case "!":
|
||||
proposals.addAll(proposeForAllKeys(query));
|
||||
break;
|
||||
if (indexTokenWithCaret > 0) {
|
||||
final String previousToken = tokens.get(indexTokenWithCaret - 1);
|
||||
switch (previousToken) {
|
||||
case "(":
|
||||
case "and":
|
||||
case "or":
|
||||
case "!":
|
||||
proposals.addAll(proposeForAllKeys(query));
|
||||
break;
|
||||
|
||||
case ")":
|
||||
default:
|
||||
// proposals.addAll(proposal);
|
||||
break;
|
||||
}
|
||||
} else if (indexTokenWithCaret == 0) {
|
||||
proposals.addAll(proposeForAllKeys(query));
|
||||
}
|
||||
case ")":
|
||||
default:
|
||||
// proposals.addAll(proposal);
|
||||
break;
|
||||
}
|
||||
} else if (indexTokenWithCaret == 0) {
|
||||
proposals.addAll(proposeForAllKeys(query));
|
||||
}
|
||||
|
||||
return proposals;
|
||||
}
|
||||
return proposals;
|
||||
}
|
||||
|
||||
private Collection<? extends Proposal> proposeForAllKeys(final QueryWithCaretMarker query) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
final String wordPrefix = wordPrefix(query.getQueryWithCaretMarker());
|
||||
private Collection<? extends Proposal> proposeForAllKeys(final QueryWithCaretMarker query) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
final String wordPrefix = wordPrefix(query.getQueryWithCaretMarker());
|
||||
|
||||
if (wordPrefix != null) {
|
||||
final SortedSet<String> allFields = queryCompletionIndex.findAllFields(query.getDateRange());
|
||||
for (final String field : allFields) {
|
||||
if (wordPrefix != null) {
|
||||
final SortedSet<String> allFields = queryCompletionIndex.findAllFields(query.getDateRange());
|
||||
for (final String field : allFields) {
|
||||
|
||||
if (!field.startsWith(wordPrefix)) {
|
||||
continue;
|
||||
}
|
||||
if (!field.startsWith(wordPrefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String queryWithCaretMarker = query.getQueryWithCaretMarker();
|
||||
final String proposedQuery = queryWithCaretMarker
|
||||
.replaceAll(REGEX_IDENTIFIER + CARET_MARKER + REGEX_IDENTIFIER, field + "=* ");
|
||||
final String newQueryWithCaretMarker = queryWithCaretMarker
|
||||
.replaceAll(REGEX_IDENTIFIER + CARET_MARKER + REGEX_IDENTIFIER, field + "=" + CARET_MARKER);
|
||||
final String newQuery = newQueryWithCaretMarker.replace(CARET_MARKER, "");
|
||||
final int newCaretPosition = newQueryWithCaretMarker.indexOf(CARET_MARKER);
|
||||
final Proposal proposal = new Proposal(field, proposedQuery, true, newQuery, newCaretPosition);
|
||||
proposals.add(proposal);
|
||||
}
|
||||
}
|
||||
final String queryWithCaretMarker = query.getQueryWithCaretMarker();
|
||||
final String proposedQuery = queryWithCaretMarker
|
||||
.replaceAll(REGEX_IDENTIFIER + CARET_MARKER + REGEX_IDENTIFIER, field + "=* ");
|
||||
final String newQueryWithCaretMarker = queryWithCaretMarker
|
||||
.replaceAll(REGEX_IDENTIFIER + CARET_MARKER + REGEX_IDENTIFIER, field + "=" + CARET_MARKER);
|
||||
final String newQuery = newQueryWithCaretMarker.replace(CARET_MARKER, "");
|
||||
final int newCaretPosition = newQueryWithCaretMarker.indexOf(CARET_MARKER);
|
||||
final Proposal proposal = new Proposal(field, proposedQuery, true, newQuery, newCaretPosition);
|
||||
proposals.add(proposal);
|
||||
}
|
||||
}
|
||||
|
||||
return proposals;
|
||||
}
|
||||
return proposals;
|
||||
}
|
||||
|
||||
private String wordPrefix(final String queryWithCaretMarker) {
|
||||
private String wordPrefix(final String queryWithCaretMarker) {
|
||||
|
||||
final Pattern pattern = Pattern.compile("(" + REGEX_IDENTIFIER + CARET_MARKER + ")");
|
||||
final Matcher matcher = pattern.matcher(queryWithCaretMarker);
|
||||
if (matcher.find()) {
|
||||
final String group = matcher.group();
|
||||
return group.replace(CARET_MARKER, "");
|
||||
}
|
||||
final Pattern pattern = Pattern.compile("(" + REGEX_IDENTIFIER + CARET_MARKER + ")");
|
||||
final Matcher matcher = pattern.matcher(queryWithCaretMarker);
|
||||
if (matcher.find()) {
|
||||
final String group = matcher.group();
|
||||
return group.replace(CARET_MARKER, "");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Proposal> proposeForAllKeys(final DateTimeRange dateRange) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
private List<Proposal> proposeForAllKeys(final DateTimeRange dateRange) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
|
||||
final SortedSet<String> allFields = queryCompletionIndex.findAllFields(dateRange);
|
||||
for (final String field : allFields) {
|
||||
final String proposedQuery = field + "=*";
|
||||
final String newQuery = field + "=";
|
||||
final int newCaretPosition = newQuery.length();
|
||||
final Proposal proposal = new Proposal(field, proposedQuery, true, newQuery, newCaretPosition);
|
||||
proposals.add(proposal);
|
||||
}
|
||||
final SortedSet<String> allFields = queryCompletionIndex.findAllFields(dateRange);
|
||||
for (final String field : allFields) {
|
||||
final String proposedQuery = field + "=*";
|
||||
final String newQuery = field + "=";
|
||||
final int newCaretPosition = newQuery.length();
|
||||
final Proposal proposal = new Proposal(field, proposedQuery, true, newQuery, newCaretPosition);
|
||||
proposals.add(proposal);
|
||||
}
|
||||
|
||||
return proposals;
|
||||
}
|
||||
return proposals;
|
||||
}
|
||||
|
||||
List<Proposal> proposalsForValues(final QueryWithCaretMarker query) {
|
||||
try {
|
||||
// Add caret marker, so that we know where the caret is.
|
||||
// This also makes sure that a query like "name=|" ('|' is the caret) can be
|
||||
// parsed.
|
||||
// Without the caret marker the query would be "name=", which is not a valid
|
||||
// expression.
|
||||
final String queryWithCaretMarker = query.getQueryWithCaretMarker();
|
||||
List<Proposal> proposalsForValues(final QueryWithCaretMarker query) {
|
||||
try {
|
||||
// Add caret marker, so that we know where the caret is.
|
||||
// This also makes sure that a query like "name=|" ('|' is the caret) can be
|
||||
// parsed.
|
||||
// Without the caret marker the query would be "name=", which is not a valid
|
||||
// expression.
|
||||
final String queryWithCaretMarker = query.getQueryWithCaretMarker();
|
||||
|
||||
// parse the query
|
||||
final Expression expression = QueryLanguageParser.parse(queryWithCaretMarker);
|
||||
// parse the query
|
||||
final Expression expression = QueryLanguageParser.parse(queryWithCaretMarker);
|
||||
|
||||
// normalize it, so that we can use the queryCompletionIndex to search for
|
||||
// candidate values
|
||||
final QueryCompletionExpressionOptimizer optimizer = new QueryCompletionExpressionOptimizer();
|
||||
final Expression normalizedExpression = optimizer.normalizeExpression(expression);
|
||||
// normalize it, so that we can use the queryCompletionIndex to search for
|
||||
// candidate values
|
||||
final QueryCompletionExpressionOptimizer optimizer = new QueryCompletionExpressionOptimizer();
|
||||
final Expression normalizedExpression = optimizer.normalizeExpression(expression);
|
||||
|
||||
// find all candidate values
|
||||
final SortedSet<String> candidateValues = normalizedExpression
|
||||
.visit(new FindValuesForQueryCompletion(query.getDateRange(), queryCompletionIndex));
|
||||
// find all candidate values
|
||||
final SortedSet<String> candidateValues = normalizedExpression
|
||||
.visit(new FindValuesForQueryCompletion(query.getDateRange(), queryCompletionIndex));
|
||||
|
||||
final SortedSet<String> sortedAndPreparedCandidateValues = resultFilter(query.getResultMode(),
|
||||
candidateValues, queryWithCaretMarker);
|
||||
final SortedSet<String> sortedAndPreparedCandidateValues = resultFilter(query.getResultMode(),
|
||||
candidateValues, queryWithCaretMarker);
|
||||
|
||||
// translate the candidate values to proposals
|
||||
final List<Proposal> proposals = generateProposals(queryWithCaretMarker, sortedAndPreparedCandidateValues);
|
||||
// translate the candidate values to proposals
|
||||
final List<Proposal> proposals = generateProposals(queryWithCaretMarker, sortedAndPreparedCandidateValues);
|
||||
|
||||
return proposals;
|
||||
} catch (final SyntaxException e) {
|
||||
LOGGER.debug("Query ({}) is not valid. This is expected to happen "
|
||||
+ "unless we are looking for proposals of values.", query, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
return proposals;
|
||||
} catch (final SyntaxException e) {
|
||||
LOGGER.debug("Query ({}) is not valid. This is expected to happen "
|
||||
+ "unless we are looking for proposals of values.", query, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private SortedSet<String> resultFilter(final ResultMode resultMode, final SortedSet<String> candidateValues,
|
||||
final String queryWithCaretMarker) {
|
||||
switch (resultMode) {
|
||||
case CUT_AT_DOT:
|
||||
return cutAtDots(candidateValues, queryWithCaretMarker);
|
||||
case FULL_VALUES:
|
||||
return candidateValues;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected value: " + resultMode);
|
||||
}
|
||||
}
|
||||
private SortedSet<String> resultFilter(final ResultMode resultMode, final SortedSet<String> candidateValues,
|
||||
final String queryWithCaretMarker) {
|
||||
switch (resultMode) {
|
||||
case CUT_AT_DOT:
|
||||
return cutAtDots(candidateValues, queryWithCaretMarker);
|
||||
case FULL_VALUES:
|
||||
return candidateValues;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected value: " + resultMode);
|
||||
}
|
||||
}
|
||||
|
||||
private SortedSet<String> cutAtDots(final SortedSet<String> candidateValues, final String queryWithCaretMarker) {
|
||||
final CandidateGrouper grouper = new CandidateGrouper();
|
||||
return grouper.group(candidateValues, queryWithCaretMarker);
|
||||
}
|
||||
private SortedSet<String> cutAtDots(final SortedSet<String> candidateValues, final String queryWithCaretMarker) {
|
||||
final CandidateGrouper grouper = new CandidateGrouper();
|
||||
return grouper.group(candidateValues, queryWithCaretMarker);
|
||||
}
|
||||
|
||||
private List<Proposal> generateProposals(final String queryWithCaretMarker,
|
||||
final SortedSet<String> candidateValues) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
private List<Proposal> generateProposals(final String queryWithCaretMarker,
|
||||
final SortedSet<String> candidateValues) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
|
||||
for (final String proposedTag : candidateValues) {
|
||||
for (final String proposedTag : candidateValues) {
|
||||
|
||||
final String proposedQueryWithCaretMarker = queryWithCaretMarker
|
||||
.replaceAll(REGEX_IDENTIFIER + CARET_MARKER + REGEX_IDENTIFIER, proposedTag + CARET_MARKER);
|
||||
final String proposedQueryWithCaretMarker = queryWithCaretMarker
|
||||
.replaceAll(REGEX_IDENTIFIER + CARET_MARKER + REGEX_IDENTIFIER, proposedTag + CARET_MARKER);
|
||||
|
||||
final String proposedQuery = proposedQueryWithCaretMarker.replace(CARET_MARKER, "");
|
||||
final int newCaretPosition = proposedQueryWithCaretMarker.indexOf(CARET_MARKER);
|
||||
final String proposedQuery = proposedQueryWithCaretMarker.replace(CARET_MARKER, "");
|
||||
final int newCaretPosition = proposedQueryWithCaretMarker.indexOf(CARET_MARKER);
|
||||
|
||||
final Proposal proposal = new Proposal(proposedTag, proposedQuery, true, proposedQuery, newCaretPosition);
|
||||
proposals.add(proposal);
|
||||
}
|
||||
final Proposal proposal = new Proposal(proposedTag, proposedQuery, true, proposedQuery, newCaretPosition);
|
||||
proposals.add(proposal);
|
||||
}
|
||||
|
||||
return proposals;
|
||||
}
|
||||
return proposals;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,228 +45,228 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class QueryCompletionExpressionOptimizer {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(QueryCompletionExpressionOptimizer.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(QueryCompletionExpressionOptimizer.class);
|
||||
|
||||
private static final class ReplaceINExpressionsWithPropertyExpressionsVisitor extends IdentityExpressionVisitor {
|
||||
private static final class ReplaceINExpressionsWithPropertyExpressionsVisitor extends IdentityExpressionVisitor {
|
||||
|
||||
@Override
|
||||
public Expression visit(final InExpression expression) {
|
||||
if (expression.containsCaret() || expression.getValues().size() == 1) {
|
||||
final String property = expression.getProperty();
|
||||
final List<String> values = expression.getValues();
|
||||
@Override
|
||||
public Expression visit(final InExpression expression) {
|
||||
if (expression.containsCaret() || expression.getValues().size() == 1) {
|
||||
final String property = expression.getProperty();
|
||||
final List<String> values = expression.getValues();
|
||||
|
||||
final List<Property> propertyExpressions = new ArrayList<>();
|
||||
final List<Property> propertyExpressions = new ArrayList<>();
|
||||
|
||||
for (final String value : values) {
|
||||
propertyExpressions.add(new Property(property, new Terminal(value)));
|
||||
}
|
||||
for (final String value : values) {
|
||||
propertyExpressions.add(new Property(property, new Terminal(value)));
|
||||
}
|
||||
|
||||
return Expression.Or.create(propertyExpressions);
|
||||
} else {
|
||||
return super.visit(expression);
|
||||
}
|
||||
};
|
||||
}
|
||||
return Expression.Or.create(propertyExpressions);
|
||||
} else {
|
||||
return super.visit(expression);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static final class RemoveOrEdExpressions extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final Or expression) {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
private static final class RemoveOrEdExpressions extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final Or expression) {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
if (left.containsCaret() && !right.containsCaret()) {
|
||||
return left;
|
||||
}
|
||||
if (!left.containsCaret() && right.containsCaret()) {
|
||||
return right;
|
||||
}
|
||||
return super.visit(expression);
|
||||
};
|
||||
}
|
||||
if (left.containsCaret() && !right.containsCaret()) {
|
||||
return left;
|
||||
}
|
||||
if (!left.containsCaret() && right.containsCaret()) {
|
||||
return right;
|
||||
}
|
||||
return super.visit(expression);
|
||||
};
|
||||
}
|
||||
|
||||
private static final class DistributiveNormalization extends IdentityExpressionVisitor {
|
||||
private static final class DistributiveNormalization extends IdentityExpressionVisitor {
|
||||
|
||||
@Override
|
||||
public Expression visit(final And expression) {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
@Override
|
||||
public Expression visit(final And expression) {
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
if (left instanceof Or) {
|
||||
// (a or b) and c
|
||||
// becomes
|
||||
// a and c or b and c
|
||||
final Expression ac = new And(((Or) left).getLeft(), right);
|
||||
final Expression bc = new And(((Or) left).getRight(), right);
|
||||
return new Or(ac, bc);
|
||||
}
|
||||
if (left instanceof Or) {
|
||||
// (a or b) and c
|
||||
// becomes
|
||||
// a and c or b and c
|
||||
final Expression ac = new And(((Or) left).getLeft(), right);
|
||||
final Expression bc = new And(((Or) left).getRight(), right);
|
||||
return new Or(ac, bc);
|
||||
}
|
||||
|
||||
if (right instanceof Or) {
|
||||
// a and (b or c)
|
||||
// becomes
|
||||
// a and b or a and c
|
||||
final Expression ab = new And(left, ((Or) right).getLeft());
|
||||
final Expression ac = new And(left, ((Or) right).getRight());
|
||||
return new Or(ab, ac);
|
||||
}
|
||||
return super.visit(expression);
|
||||
};
|
||||
}
|
||||
if (right instanceof Or) {
|
||||
// a and (b or c)
|
||||
// becomes
|
||||
// a and b or a and c
|
||||
final Expression ab = new And(left, ((Or) right).getLeft());
|
||||
final Expression ac = new And(left, ((Or) right).getRight());
|
||||
return new Or(ab, ac);
|
||||
}
|
||||
return super.visit(expression);
|
||||
};
|
||||
}
|
||||
|
||||
private static final class RotateAndExpressions extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final And expression) {
|
||||
private static final class RotateAndExpressions extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final And expression) {
|
||||
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
// (| and a) and b => | and (a and b)
|
||||
//
|
||||
// The expression with the caret is moved up
|
||||
if (left.containsCaret() && left instanceof And) {
|
||||
final Expression leftLeft = ((And) left).getLeft();
|
||||
final Expression leftRight = ((And) left).getRight();
|
||||
// (| and a) and b => | and (a and b)
|
||||
//
|
||||
// The expression with the caret is moved up
|
||||
if (left.containsCaret() && left instanceof And) {
|
||||
final Expression leftLeft = ((And) left).getLeft();
|
||||
final Expression leftRight = ((And) left).getRight();
|
||||
|
||||
if (leftLeft.containsCaret()) {
|
||||
return new And(leftLeft, new And(leftRight, right));
|
||||
} else {
|
||||
return new And(new And(leftLeft, right), leftRight);
|
||||
}
|
||||
} else if (right.containsCaret() && right instanceof And) {
|
||||
final Expression rightLeft = ((And) right).getLeft();
|
||||
final Expression rightRight = ((And) right).getRight();
|
||||
if (leftLeft.containsCaret()) {
|
||||
return new And(leftLeft, new And(leftRight, right));
|
||||
} else {
|
||||
return new And(new And(leftLeft, right), leftRight);
|
||||
}
|
||||
} else if (right.containsCaret() && right instanceof And) {
|
||||
final Expression rightLeft = ((And) right).getLeft();
|
||||
final Expression rightRight = ((And) right).getRight();
|
||||
|
||||
if (rightLeft.containsCaret()) {
|
||||
return new And(rightLeft, new And(rightRight, left));
|
||||
} else {
|
||||
return new And(new And(rightLeft, left), rightRight);
|
||||
}
|
||||
}
|
||||
if (rightLeft.containsCaret()) {
|
||||
return new And(rightLeft, new And(rightRight, left));
|
||||
} else {
|
||||
return new And(new And(rightLeft, left), rightRight);
|
||||
}
|
||||
}
|
||||
|
||||
return super.visit(expression);
|
||||
}
|
||||
}
|
||||
return super.visit(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DoubleNegationExpressions extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final Not expression) {
|
||||
if (expression instanceof Not) {
|
||||
if (expression.getExpression() instanceof Not) {
|
||||
return ((Not) expression.getExpression()).getExpression();
|
||||
}
|
||||
}
|
||||
return super.visit(expression);
|
||||
}
|
||||
}
|
||||
private static final class DoubleNegationExpressions extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final Not expression) {
|
||||
if (expression instanceof Not) {
|
||||
if (expression.getExpression() instanceof Not) {
|
||||
return ((Not) expression.getExpression()).getExpression();
|
||||
}
|
||||
}
|
||||
return super.visit(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DeMorgan extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final Not expression) {
|
||||
private static final class DeMorgan extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final Not expression) {
|
||||
|
||||
if (expression.getExpression() instanceof And) {
|
||||
final And andExpression = (And) expression.getExpression();
|
||||
final Expression left = andExpression.getLeft();
|
||||
final Expression right = andExpression.getRight();
|
||||
if (expression.getExpression() instanceof And) {
|
||||
final And andExpression = (And) expression.getExpression();
|
||||
final Expression left = andExpression.getLeft();
|
||||
final Expression right = andExpression.getRight();
|
||||
|
||||
final Expression notLeft = new Not(left);
|
||||
final Expression notRight = new Not(right);
|
||||
final Expression notLeft = new Not(left);
|
||||
final Expression notRight = new Not(right);
|
||||
|
||||
return new Or(notLeft, notRight);
|
||||
}
|
||||
return new Or(notLeft, notRight);
|
||||
}
|
||||
|
||||
return super.visit(expression);
|
||||
}
|
||||
}
|
||||
return super.visit(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ToAndCaretExpressions extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final And expression) {
|
||||
private static final class ToAndCaretExpressions extends IdentityExpressionVisitor {
|
||||
@Override
|
||||
public Expression visit(final And expression) {
|
||||
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
final Expression left = expression.getLeft();
|
||||
final Expression right = expression.getRight();
|
||||
|
||||
if (left.containsCaret() && left instanceof Property) {
|
||||
return new AndCaretExpression((Property) left, right);
|
||||
}
|
||||
if (right.containsCaret() && right instanceof Property) {
|
||||
return new AndCaretExpression((Property) right, left);
|
||||
}
|
||||
if (left.containsCaret() && left instanceof Property) {
|
||||
return new AndCaretExpression((Property) left, right);
|
||||
}
|
||||
if (right.containsCaret() && right instanceof Property) {
|
||||
return new AndCaretExpression((Property) right, left);
|
||||
}
|
||||
|
||||
if (left.containsCaret()//
|
||||
&& left instanceof Not//
|
||||
&& ((Not) left).getExpression() instanceof Property) {
|
||||
return new AndNotCaretExpression((Property) ((Not) left).getExpression(), right);
|
||||
}
|
||||
if (right.containsCaret()//
|
||||
&& right instanceof Not//
|
||||
&& ((Not) right).getExpression() instanceof Property) {
|
||||
return new AndNotCaretExpression((Property) ((Not) right).getExpression(), left);
|
||||
}
|
||||
if (left.containsCaret()//
|
||||
&& left instanceof Not//
|
||||
&& ((Not) left).getExpression() instanceof Property) {
|
||||
return new AndNotCaretExpression((Property) ((Not) left).getExpression(), right);
|
||||
}
|
||||
if (right.containsCaret()//
|
||||
&& right instanceof Not//
|
||||
&& ((Not) right).getExpression() instanceof Property) {
|
||||
return new AndNotCaretExpression((Property) ((Not) right).getExpression(), left);
|
||||
}
|
||||
|
||||
return super.visit(expression);
|
||||
}
|
||||
}
|
||||
return super.visit(expression);
|
||||
}
|
||||
}
|
||||
|
||||
public Expression normalizeExpression(final Expression expression) {
|
||||
public Expression normalizeExpression(final Expression expression) {
|
||||
|
||||
Expression normalizingExpression = expression;
|
||||
Expression previousExpression = normalizingExpression;
|
||||
do {
|
||||
previousExpression = normalizingExpression;
|
||||
// replace all IN-expression, because they are just syntactic sugar for
|
||||
// OR-expressions, but only for those that include the caret
|
||||
normalizingExpression = normalizingExpression
|
||||
.visit(new ReplaceINExpressionsWithPropertyExpressionsVisitor());
|
||||
Expression normalizingExpression = expression;
|
||||
Expression previousExpression = normalizingExpression;
|
||||
do {
|
||||
previousExpression = normalizingExpression;
|
||||
// replace all IN-expression, because they are just syntactic sugar for
|
||||
// OR-expressions, but only for those that include the caret
|
||||
normalizingExpression = normalizingExpression
|
||||
.visit(new ReplaceINExpressionsWithPropertyExpressionsVisitor());
|
||||
|
||||
// Remove expressions that are OR'ed with the one that contains the caret.
|
||||
// Everything that is OR'ed with the 'caret'-expression cannot change the
|
||||
// possible values.
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new RemoveOrEdExpressions());
|
||||
// Remove expressions that are OR'ed with the one that contains the caret.
|
||||
// Everything that is OR'ed with the 'caret'-expression cannot change the
|
||||
// possible values.
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new RemoveOrEdExpressions());
|
||||
|
||||
// In the end we want to have expressions like "firstname=Jane and lastname=|".
|
||||
// To reach that goal we use the distributive law to modify expressions like
|
||||
// "(firstname=Jane or firstname=John) and lastname=|" to "(firstname=Jane and
|
||||
// lastname=|) or (firstname=John and lastname=|)"
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new DistributiveNormalization());
|
||||
// In the end we want to have expressions like "firstname=Jane and lastname=|".
|
||||
// To reach that goal we use the distributive law to modify expressions like
|
||||
// "(firstname=Jane or firstname=John) and lastname=|" to "(firstname=Jane and
|
||||
// lastname=|) or (firstname=John and lastname=|)"
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new DistributiveNormalization());
|
||||
|
||||
// (fn=John and (fn=John and ln=|)
|
||||
// normalized to
|
||||
// (fn=John and ln=|) and (fn=Jane and ln=|)
|
||||
// or normalized to
|
||||
// (fn=John and fn=Jane) and ln=|
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new RotateAndExpressions());
|
||||
// (fn=John and (fn=John and ln=|)
|
||||
// normalized to
|
||||
// (fn=John and ln=|) and (fn=Jane and ln=|)
|
||||
// or normalized to
|
||||
// (fn=John and fn=Jane) and ln=|
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new RotateAndExpressions());
|
||||
|
||||
// normalize a NAND-expression into an OR with DeMorgan, the OR-Expression might
|
||||
// later be removed
|
||||
// not ( a and b) => (not a) or (not b)
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new DeMorgan());
|
||||
// normalize a NAND-expression into an OR with DeMorgan, the OR-Expression might
|
||||
// later be removed
|
||||
// not ( a and b) => (not a) or (not b)
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new DeMorgan());
|
||||
|
||||
// remove double negation
|
||||
// not not a => a
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new DoubleNegationExpressions());
|
||||
} while (!normalizingExpression.equals(previousExpression));
|
||||
// remove double negation
|
||||
// not not a => a
|
||||
normalizingExpression = visitRepeatedly(normalizingExpression, new DoubleNegationExpressions());
|
||||
} while (!normalizingExpression.equals(previousExpression));
|
||||
|
||||
// Replaces all (a and |) expressions with a special expression that represents
|
||||
// it.
|
||||
// This special expression will then be used during evaluation.
|
||||
return visitRepeatedly(normalizingExpression, new ToAndCaretExpressions());
|
||||
}
|
||||
// Replaces all (a and |) expressions with a special expression that represents
|
||||
// it.
|
||||
// This special expression will then be used during evaluation.
|
||||
return visitRepeatedly(normalizingExpression, new ToAndCaretExpressions());
|
||||
}
|
||||
|
||||
private static Expression visitRepeatedly(final Expression expression,
|
||||
final ExpressionVisitor<Expression> visitor) {
|
||||
Expression previousExpression;
|
||||
Expression result = expression;
|
||||
private static Expression visitRepeatedly(final Expression expression,
|
||||
final ExpressionVisitor<Expression> visitor) {
|
||||
Expression previousExpression;
|
||||
Expression result = expression;
|
||||
|
||||
do {
|
||||
previousExpression = result;
|
||||
result = previousExpression.visit(visitor);
|
||||
if (!previousExpression.equals(result)) {
|
||||
LOGGER.debug(" translate: {}", visitor.getClass().getSimpleName());
|
||||
LOGGER.debug(" in: {}", previousExpression);
|
||||
LOGGER.debug(" out: {}", result);
|
||||
}
|
||||
} while (!previousExpression.equals(result));
|
||||
do {
|
||||
previousExpression = result;
|
||||
result = previousExpression.visit(visitor);
|
||||
if (!previousExpression.equals(result)) {
|
||||
LOGGER.debug(" translate: {}", visitor.getClass().getSimpleName());
|
||||
LOGGER.debug(" in: {}", previousExpression);
|
||||
LOGGER.debug(" out: {}", result);
|
||||
}
|
||||
} while (!previousExpression.equals(result));
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,125 +28,125 @@ import org.lucares.utils.CollectionUtils;
|
||||
|
||||
public class QueryLanguage {
|
||||
|
||||
public Expression parse(final String input) {
|
||||
// define the input
|
||||
final CharStream in = CharStreams.fromString(input);
|
||||
public Expression parse(final String input) {
|
||||
// define the input
|
||||
final CharStream in = CharStreams.fromString(input);
|
||||
|
||||
// create lexer and parser
|
||||
final PdbLangLexer lexer = new PdbLangLexer(in);
|
||||
lexer.addErrorListener(new ErrorListener());
|
||||
// create lexer and parser
|
||||
final PdbLangLexer lexer = new PdbLangLexer(in);
|
||||
lexer.addErrorListener(new ErrorListener());
|
||||
|
||||
final CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
final PdbLangParser parser = new PdbLangParser(tokens);
|
||||
parser.addErrorListener(new ErrorListener());
|
||||
final CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
final PdbLangParser parser = new PdbLangParser(tokens);
|
||||
parser.addErrorListener(new ErrorListener());
|
||||
|
||||
final Stack<Expression> stack = new Stack<>();
|
||||
final Stack<Expression> stack = new Stack<>();
|
||||
|
||||
// define a listener that is called for every terminals and
|
||||
// non-terminals
|
||||
final ParseTreeListener listener = new PdbLangBaseListener() {
|
||||
// define a listener that is called for every terminals and
|
||||
// non-terminals
|
||||
final ParseTreeListener listener = new PdbLangBaseListener() {
|
||||
|
||||
@Override
|
||||
public void exitIdentifierExpression(final IdentifierExpressionContext ctx) {
|
||||
if (ctx.getText().length() > 255) {
|
||||
throw new SyntaxException(ctx, "token too long");
|
||||
}
|
||||
@Override
|
||||
public void exitIdentifierExpression(final IdentifierExpressionContext ctx) {
|
||||
if (ctx.getText().length() > 255) {
|
||||
throw new SyntaxException(ctx, "token too long");
|
||||
}
|
||||
|
||||
stack.push(new Terminal(ctx.getText()));
|
||||
}
|
||||
stack.push(new Terminal(ctx.getText()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitPropertyTerminalExpression(final PropertyTerminalExpressionContext ctx) {
|
||||
if (ctx.getText().length() > 255) {
|
||||
throw new SyntaxException(ctx, "token too long");
|
||||
}
|
||||
@Override
|
||||
public void exitPropertyTerminalExpression(final PropertyTerminalExpressionContext ctx) {
|
||||
if (ctx.getText().length() > 255) {
|
||||
throw new SyntaxException(ctx, "token too long");
|
||||
}
|
||||
|
||||
stack.push(new Terminal(ctx.getText()));
|
||||
}
|
||||
stack.push(new Terminal(ctx.getText()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitNotExpression(final NotExpressionContext ctx) {
|
||||
@Override
|
||||
public void exitNotExpression(final NotExpressionContext ctx) {
|
||||
|
||||
final Expression expression = stack.pop();
|
||||
final Expression expression = stack.pop();
|
||||
|
||||
final Expression notExpression = new Not(expression);
|
||||
stack.push(notExpression);
|
||||
}
|
||||
final Expression notExpression = new Not(expression);
|
||||
stack.push(notExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitBinaryAndExpression(final BinaryAndExpressionContext ctx) {
|
||||
final Expression right = stack.pop();
|
||||
final TemporaryExpression operation = new AndTemporary();
|
||||
final Expression left = stack.pop();
|
||||
@Override
|
||||
public void exitBinaryAndExpression(final BinaryAndExpressionContext ctx) {
|
||||
final Expression right = stack.pop();
|
||||
final TemporaryExpression operation = new AndTemporary();
|
||||
final Expression left = stack.pop();
|
||||
|
||||
stack.push(operation.toExpression(left, right));
|
||||
}
|
||||
stack.push(operation.toExpression(left, right));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitBinaryOrExpression(final BinaryOrExpressionContext ctx) {
|
||||
final Expression right = stack.pop();
|
||||
final TemporaryExpression operation = new OrTemporary();
|
||||
final Expression left = stack.pop();
|
||||
@Override
|
||||
public void exitBinaryOrExpression(final BinaryOrExpressionContext ctx) {
|
||||
final Expression right = stack.pop();
|
||||
final TemporaryExpression operation = new OrTemporary();
|
||||
final Expression left = stack.pop();
|
||||
|
||||
stack.push(operation.toExpression(left, right));
|
||||
}
|
||||
stack.push(operation.toExpression(left, right));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitListOfPropValues(final ListOfPropValuesContext ctx) {
|
||||
final Expression topStackElement = stack.pop();
|
||||
@Override
|
||||
public void exitListOfPropValues(final ListOfPropValuesContext ctx) {
|
||||
final Expression topStackElement = stack.pop();
|
||||
|
||||
if (topStackElement instanceof ListOfPropertyValues) {
|
||||
// there are at least two property values in the query
|
||||
// e.g. in the expression "bird in (eagle, pigeon)"
|
||||
final ListOfPropertyValues existingList = (ListOfPropertyValues) topStackElement;
|
||||
final Terminal nextPropertyValue = (Terminal) stack.pop();
|
||||
if (topStackElement instanceof ListOfPropertyValues) {
|
||||
// there are at least two property values in the query
|
||||
// e.g. in the expression "bird in (eagle, pigeon)"
|
||||
final ListOfPropertyValues existingList = (ListOfPropertyValues) topStackElement;
|
||||
final Terminal nextPropertyValue = (Terminal) stack.pop();
|
||||
|
||||
final ListOfPropertyValues newListOfPropertyValues = new ListOfPropertyValues(nextPropertyValue,
|
||||
existingList);
|
||||
stack.push(newListOfPropertyValues);
|
||||
} else {
|
||||
// this is the first or the only value in this list of property values
|
||||
// e.g. in the expression "bird in (eagle)"
|
||||
final Terminal propertyValue = (Terminal) topStackElement;
|
||||
final ListOfPropertyValues newListOfPropertyValues = new ListOfPropertyValues(nextPropertyValue,
|
||||
existingList);
|
||||
stack.push(newListOfPropertyValues);
|
||||
} else {
|
||||
// this is the first or the only value in this list of property values
|
||||
// e.g. in the expression "bird in (eagle)"
|
||||
final Terminal propertyValue = (Terminal) topStackElement;
|
||||
|
||||
final ListOfPropertyValues newListOfPropertyValues = new ListOfPropertyValues(propertyValue);
|
||||
stack.push(newListOfPropertyValues);
|
||||
}
|
||||
}
|
||||
final ListOfPropertyValues newListOfPropertyValues = new ListOfPropertyValues(propertyValue);
|
||||
stack.push(newListOfPropertyValues);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitEnclosedListOfPropValues(final EnclosedListOfPropValuesContext ctx) {
|
||||
@Override
|
||||
public void exitEnclosedListOfPropValues(final EnclosedListOfPropValuesContext ctx) {
|
||||
|
||||
final ListOfPropertyValues propertyValues = (ListOfPropertyValues) stack.pop();
|
||||
final Terminal propertyName = (Terminal) stack.pop();
|
||||
final ListOfPropertyValues propertyValues = (ListOfPropertyValues) stack.pop();
|
||||
final Terminal propertyName = (Terminal) stack.pop();
|
||||
|
||||
final InExpression inExpression = new InExpression(propertyName.getValue(), propertyValues.getValues());
|
||||
stack.push(inExpression);
|
||||
}
|
||||
};
|
||||
final InExpression inExpression = new InExpression(propertyName.getValue(), propertyValues.getValues());
|
||||
stack.push(inExpression);
|
||||
}
|
||||
};
|
||||
|
||||
// Specify our entry point
|
||||
final ParseTree parseTree = parser.start();
|
||||
// Specify our entry point
|
||||
final ParseTree parseTree = parser.start();
|
||||
|
||||
// Walk it and attach our listener
|
||||
final ParseTreeWalker walker = new ParseTreeWalker();
|
||||
walker.walk(listener, parseTree);
|
||||
// Walk it and attach our listener
|
||||
final ParseTreeWalker walker = new ParseTreeWalker();
|
||||
walker.walk(listener, parseTree);
|
||||
|
||||
if (stack.size() != 1) {
|
||||
throw new RuntimeException("stack should have exactly one element " + stack);
|
||||
}
|
||||
if (stack.size() != 1) {
|
||||
throw new RuntimeException("stack should have exactly one element " + stack);
|
||||
}
|
||||
|
||||
return stack.pop();
|
||||
}
|
||||
return stack.pop();
|
||||
}
|
||||
|
||||
public static List<String> getTokens(final String input) {
|
||||
final CharStream in = CharStreams.fromString(input);
|
||||
public static List<String> getTokens(final String input) {
|
||||
final CharStream in = CharStreams.fromString(input);
|
||||
|
||||
final PdbLangLexer lexer = new PdbLangLexer(in);
|
||||
final PdbLangLexer lexer = new PdbLangLexer(in);
|
||||
|
||||
final CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
tokens.fill();
|
||||
final List<Token> tokenList = tokens.getTokens();
|
||||
return CollectionUtils.map(tokenList, Token::getText);
|
||||
}
|
||||
final CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
tokens.fill();
|
||||
final List<Token> tokenList = tokens.getTokens();
|
||||
return CollectionUtils.map(tokenList, Token::getText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ package org.lucares.pdb.datastore.lang;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class QueryLanguageParser {
|
||||
public static Expression parse(final String query) {
|
||||
public static Expression parse(final String query) {
|
||||
|
||||
final Expression result;
|
||||
if (StringUtils.isEmpty(query)) {
|
||||
result = Expression.matchAll();
|
||||
} else {
|
||||
final QueryLanguage lang = new QueryLanguage();
|
||||
result = lang.parse(query);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
final Expression result;
|
||||
if (StringUtils.isEmpty(query)) {
|
||||
result = Expression.matchAll();
|
||||
} else {
|
||||
final QueryLanguage lang = new QueryLanguage();
|
||||
result = lang.parse(query);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,61 +4,61 @@ import org.antlr.v4.runtime.ParserRuleContext;
|
||||
|
||||
public class SyntaxException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private int lineStart;
|
||||
private int startIndex;
|
||||
private int lineStop;
|
||||
private int stopIndex;
|
||||
private static final long serialVersionUID = 1L;
|
||||
private int lineStart;
|
||||
private int startIndex;
|
||||
private int lineStop;
|
||||
private int stopIndex;
|
||||
|
||||
public SyntaxException(final ParserRuleContext context, final String message) {
|
||||
this(message, context.getStart().getLine(), context.getStart().getStartIndex(), context.getStop().getLine(),
|
||||
context.getStop().getStopIndex());
|
||||
}
|
||||
public SyntaxException(final ParserRuleContext context, final String message) {
|
||||
this(message, context.getStart().getLine(), context.getStart().getStartIndex(), context.getStop().getLine(),
|
||||
context.getStop().getStopIndex());
|
||||
}
|
||||
|
||||
public SyntaxException(final String message, final int lineStart, final int startIndex, final int lineStop,
|
||||
final int stopIndex) {
|
||||
super(message + ": " + generateMessage(lineStart, startIndex, lineStop, stopIndex));
|
||||
this.lineStart = lineStart;
|
||||
this.startIndex = startIndex;
|
||||
this.lineStop = lineStop;
|
||||
this.stopIndex = stopIndex;
|
||||
}
|
||||
public SyntaxException(final String message, final int lineStart, final int startIndex, final int lineStop,
|
||||
final int stopIndex) {
|
||||
super(message + ": " + generateMessage(lineStart, startIndex, lineStop, stopIndex));
|
||||
this.lineStart = lineStart;
|
||||
this.startIndex = startIndex;
|
||||
this.lineStop = lineStop;
|
||||
this.stopIndex = stopIndex;
|
||||
}
|
||||
|
||||
private static String generateMessage(final int lineStart, final int startIndex, final int lineStop,
|
||||
final int stopIndex) {
|
||||
private static String generateMessage(final int lineStart, final int startIndex, final int lineStop,
|
||||
final int stopIndex) {
|
||||
|
||||
return String.format("line=%d, start=%d, to line=%d stop=%d", lineStart, startIndex, lineStop, stopIndex);
|
||||
}
|
||||
return String.format("line=%d, start=%d, to line=%d stop=%d", lineStart, startIndex, lineStop, stopIndex);
|
||||
}
|
||||
|
||||
public int getLineStart() {
|
||||
return lineStart;
|
||||
}
|
||||
public int getLineStart() {
|
||||
return lineStart;
|
||||
}
|
||||
|
||||
public void setLineStart(final int lineStart) {
|
||||
this.lineStart = lineStart;
|
||||
}
|
||||
public void setLineStart(final int lineStart) {
|
||||
this.lineStart = lineStart;
|
||||
}
|
||||
|
||||
public int getStartIndex() {
|
||||
return startIndex;
|
||||
}
|
||||
public int getStartIndex() {
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
public void setStartIndex(final int startIndex) {
|
||||
this.startIndex = startIndex;
|
||||
}
|
||||
public void setStartIndex(final int startIndex) {
|
||||
this.startIndex = startIndex;
|
||||
}
|
||||
|
||||
public int getLineStop() {
|
||||
return lineStop;
|
||||
}
|
||||
public int getLineStop() {
|
||||
return lineStop;
|
||||
}
|
||||
|
||||
public void setLineStop(final int lineStop) {
|
||||
this.lineStop = lineStop;
|
||||
}
|
||||
public void setLineStop(final int lineStop) {
|
||||
this.lineStop = lineStop;
|
||||
}
|
||||
|
||||
public int getStopIndex() {
|
||||
return stopIndex;
|
||||
}
|
||||
public int getStopIndex() {
|
||||
return stopIndex;
|
||||
}
|
||||
|
||||
public void setStopIndex(final int stopIndex) {
|
||||
this.stopIndex = stopIndex;
|
||||
}
|
||||
public void setStopIndex(final int stopIndex) {
|
||||
this.stopIndex = stopIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,293 +42,293 @@ import org.testng.annotations.Test;
|
||||
|
||||
@Test
|
||||
public class DataStoreTest {
|
||||
private Path dataDirectory;
|
||||
private DataStore dataStore;
|
||||
private Map<Tags, Long> tagsToBlockStorageRootBlockNumber;
|
||||
|
||||
@BeforeMethod
|
||||
public void beforeMethod() throws IOException {
|
||||
dataDirectory = Files.createTempDirectory("pdb");
|
||||
}
|
||||
|
||||
@AfterMethod
|
||||
public void afterMethod() throws IOException {
|
||||
FileUtils.delete(dataDirectory);
|
||||
dataStore = null;
|
||||
tagsToBlockStorageRootBlockNumber = null;
|
||||
Tags.STRING_COMPRESSOR = null;
|
||||
}
|
||||
|
||||
public void testQuery() throws Exception {
|
||||
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
final DateTimeRange dateRange = DateTimeRange.relativeHours(1);
|
||||
final ParititionId partitionId = DateIndexExtension.toPartitionIds(dateRange).get(0);
|
||||
|
||||
final Tags eagleTim = Tags.createAndAddToDictionary("bird", "eagle", "name", "Tim");
|
||||
final Tags pigeonJennifer = Tags.createAndAddToDictionary("bird", "pigeon", "name", "Jennifer");
|
||||
final Tags flamingoJennifer = Tags.createAndAddToDictionary("bird", "flamingo", "name", "Jennifer");
|
||||
final Tags labradorJenny = Tags.createAndAddToDictionary("dog", "labrador", "name", "Jenny");
|
||||
final Tags labradorTim = Tags.createAndAddToDictionary("dog", "labrador", "name", "Tim");
|
||||
|
||||
tagsToBlockStorageRootBlockNumber = new HashMap<>();
|
||||
tagsToBlockStorageRootBlockNumber.put(eagleTim, dataStore.createNewFile(partitionId, eagleTim));
|
||||
tagsToBlockStorageRootBlockNumber.put(pigeonJennifer, dataStore.createNewFile(partitionId, pigeonJennifer));
|
||||
tagsToBlockStorageRootBlockNumber.put(flamingoJennifer, dataStore.createNewFile(partitionId, flamingoJennifer));
|
||||
tagsToBlockStorageRootBlockNumber.put(labradorJenny, dataStore.createNewFile(partitionId, labradorJenny));
|
||||
tagsToBlockStorageRootBlockNumber.put(labradorTim, dataStore.createNewFile(partitionId, labradorTim));
|
||||
|
||||
assertSearch(dateRange, "bird=eagle", eagleTim);
|
||||
assertSearch(dateRange, "dog=labrador", labradorJenny, labradorTim);
|
||||
assertSearch(dateRange, "name=Tim", eagleTim, labradorTim);
|
||||
assertSearch(dateRange, "dog=labrador and name=Tim", labradorTim);
|
||||
assertSearch(dateRange, "dog=labrador and !name=Tim", labradorJenny);
|
||||
assertSearch(dateRange, "name=Jennifer or name=Jenny", pigeonJennifer, flamingoJennifer, labradorJenny);
|
||||
private Path dataDirectory;
|
||||
private DataStore dataStore;
|
||||
private Map<Tags, Long> tagsToBlockStorageRootBlockNumber;
|
||||
|
||||
@BeforeMethod
|
||||
public void beforeMethod() throws IOException {
|
||||
dataDirectory = Files.createTempDirectory("pdb");
|
||||
}
|
||||
|
||||
@AfterMethod
|
||||
public void afterMethod() throws IOException {
|
||||
FileUtils.delete(dataDirectory);
|
||||
dataStore = null;
|
||||
tagsToBlockStorageRootBlockNumber = null;
|
||||
Tags.STRING_COMPRESSOR = null;
|
||||
}
|
||||
|
||||
public void testQuery() throws Exception {
|
||||
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
final DateTimeRange dateRange = DateTimeRange.relativeHours(1);
|
||||
final ParititionId partitionId = DateIndexExtension.toPartitionIds(dateRange).get(0);
|
||||
|
||||
final Tags eagleTim = Tags.createAndAddToDictionary("bird", "eagle", "name", "Tim");
|
||||
final Tags pigeonJennifer = Tags.createAndAddToDictionary("bird", "pigeon", "name", "Jennifer");
|
||||
final Tags flamingoJennifer = Tags.createAndAddToDictionary("bird", "flamingo", "name", "Jennifer");
|
||||
final Tags labradorJenny = Tags.createAndAddToDictionary("dog", "labrador", "name", "Jenny");
|
||||
final Tags labradorTim = Tags.createAndAddToDictionary("dog", "labrador", "name", "Tim");
|
||||
|
||||
tagsToBlockStorageRootBlockNumber = new HashMap<>();
|
||||
tagsToBlockStorageRootBlockNumber.put(eagleTim, dataStore.createNewFile(partitionId, eagleTim));
|
||||
tagsToBlockStorageRootBlockNumber.put(pigeonJennifer, dataStore.createNewFile(partitionId, pigeonJennifer));
|
||||
tagsToBlockStorageRootBlockNumber.put(flamingoJennifer, dataStore.createNewFile(partitionId, flamingoJennifer));
|
||||
tagsToBlockStorageRootBlockNumber.put(labradorJenny, dataStore.createNewFile(partitionId, labradorJenny));
|
||||
tagsToBlockStorageRootBlockNumber.put(labradorTim, dataStore.createNewFile(partitionId, labradorTim));
|
||||
|
||||
assertSearch(dateRange, "bird=eagle", eagleTim);
|
||||
assertSearch(dateRange, "dog=labrador", labradorJenny, labradorTim);
|
||||
assertSearch(dateRange, "name=Tim", eagleTim, labradorTim);
|
||||
assertSearch(dateRange, "dog=labrador and name=Tim", labradorTim);
|
||||
assertSearch(dateRange, "dog=labrador and !name=Tim", labradorJenny);
|
||||
assertSearch(dateRange, "name=Jennifer or name=Jenny", pigeonJennifer, flamingoJennifer, labradorJenny);
|
||||
|
||||
// a͟n͟d binds stronger than o͟r
|
||||
assertSearch(dateRange, "name=Tim and dog=labrador or bird=pigeon", pigeonJennifer, labradorTim);
|
||||
assertSearch(dateRange, "bird=pigeon or name=Tim and dog=labrador", pigeonJennifer, labradorTim);
|
||||
// a͟n͟d binds stronger than o͟r
|
||||
assertSearch(dateRange, "name=Tim and dog=labrador or bird=pigeon", pigeonJennifer, labradorTim);
|
||||
assertSearch(dateRange, "bird=pigeon or name=Tim and dog=labrador", pigeonJennifer, labradorTim);
|
||||
|
||||
// parenthesis override priority of a͟n͟d
|
||||
assertSearch(dateRange, "name=Tim and (dog=labrador or bird=pigeon)", labradorTim);
|
||||
assertSearch(dateRange, "(dog=labrador or bird=pigeon) and name=Tim", labradorTim);
|
||||
// parenthesis override priority of a͟n͟d
|
||||
assertSearch(dateRange, "name=Tim and (dog=labrador or bird=pigeon)", labradorTim);
|
||||
assertSearch(dateRange, "(dog=labrador or bird=pigeon) and name=Tim", labradorTim);
|
||||
|
||||
// wildcards
|
||||
assertSearch(dateRange, "bird=*", eagleTim, pigeonJennifer, flamingoJennifer);
|
||||
assertSearch(dateRange, "name=Jen*", pigeonJennifer, flamingoJennifer, labradorJenny);
|
||||
assertSearch(dateRange, "dog=*dor", labradorJenny, labradorTim);
|
||||
assertSearch(dateRange, "dog=lab*dor", labradorJenny, labradorTim);
|
||||
assertSearch(dateRange, "dog=*lab*dor*", labradorJenny, labradorTim);
|
||||
// wildcards
|
||||
assertSearch(dateRange, "bird=*", eagleTim, pigeonJennifer, flamingoJennifer);
|
||||
assertSearch(dateRange, "name=Jen*", pigeonJennifer, flamingoJennifer, labradorJenny);
|
||||
assertSearch(dateRange, "dog=*dor", labradorJenny, labradorTim);
|
||||
assertSearch(dateRange, "dog=lab*dor", labradorJenny, labradorTim);
|
||||
assertSearch(dateRange, "dog=*lab*dor*", labradorJenny, labradorTim);
|
||||
|
||||
// 'in' queries
|
||||
assertSearch(dateRange, "bird=(eagle, pigeon, flamingo)", eagleTim, pigeonJennifer, flamingoJennifer);
|
||||
assertSearch(dateRange, "dog = (labrador) and name =Tim,Jennifer", labradorTim);
|
||||
assertSearch(dateRange, "name =Jenn*", pigeonJennifer, flamingoJennifer, labradorJenny);
|
||||
assertSearch(dateRange, "name = (*) and dog=labrador", labradorJenny, labradorTim);
|
||||
assertSearch(dateRange, "name =XYZ, * and dog=labrador", labradorJenny, labradorTim);
|
||||
// 'in' queries
|
||||
assertSearch(dateRange, "bird=(eagle, pigeon, flamingo)", eagleTim, pigeonJennifer, flamingoJennifer);
|
||||
assertSearch(dateRange, "dog = (labrador) and name =Tim,Jennifer", labradorTim);
|
||||
assertSearch(dateRange, "name =Jenn*", pigeonJennifer, flamingoJennifer, labradorJenny);
|
||||
assertSearch(dateRange, "name = (*) and dog=labrador", labradorJenny, labradorTim);
|
||||
assertSearch(dateRange, "name =XYZ, * and dog=labrador", labradorJenny, labradorTim);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetByTags() throws IOException {
|
||||
public void testGetByTags() throws IOException {
|
||||
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
tagsToBlockStorageRootBlockNumber = new LinkedHashMap<>();
|
||||
final Tags pigeonJennifer = Tags.createAndAddToDictionary("bird", "pigeon", "name", "Jennifer");
|
||||
final Tags flamingoJennifer = Tags.createAndAddToDictionary("bird", "flamingo", "name", "Jennifer");
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
tagsToBlockStorageRootBlockNumber = new LinkedHashMap<>();
|
||||
final Tags pigeonJennifer = Tags.createAndAddToDictionary("bird", "pigeon", "name", "Jennifer");
|
||||
final Tags flamingoJennifer = Tags.createAndAddToDictionary("bird", "flamingo", "name", "Jennifer");
|
||||
|
||||
final ParititionId partitionId = new ParititionId("partitionA");
|
||||
tagsToBlockStorageRootBlockNumber.put(pigeonJennifer, dataStore.createNewFile(partitionId, pigeonJennifer));
|
||||
tagsToBlockStorageRootBlockNumber.put(flamingoJennifer, dataStore.createNewFile(partitionId, flamingoJennifer));
|
||||
final ParititionId partitionId = new ParititionId("partitionA");
|
||||
tagsToBlockStorageRootBlockNumber.put(pigeonJennifer, dataStore.createNewFile(partitionId, pigeonJennifer));
|
||||
tagsToBlockStorageRootBlockNumber.put(flamingoJennifer, dataStore.createNewFile(partitionId, flamingoJennifer));
|
||||
|
||||
final Optional<Doc> docsFlamingoJennifer = dataStore.getByTags(partitionId, flamingoJennifer);
|
||||
Assert.assertTrue(docsFlamingoJennifer.isPresent(), "doc for docsFlamingoJennifer");
|
||||
}
|
||||
final Optional<Doc> docsFlamingoJennifer = dataStore.getByTags(partitionId, flamingoJennifer);
|
||||
Assert.assertTrue(docsFlamingoJennifer.isPresent(), "doc for docsFlamingoJennifer");
|
||||
}
|
||||
|
||||
public void testBlockAlignment() throws IOException {
|
||||
public void testBlockAlignment() throws IOException {
|
||||
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
final Tags eagleTim = Tags.createAndAddToDictionary("bird", "eagle", "name", "Tim");
|
||||
final long eagleTimBlockOffset = dataStore.createNewFile(new ParititionId("partitionA"), eagleTim);
|
||||
Assert.assertEquals(eagleTimBlockOffset % BSFile.BLOCK_SIZE, 0);
|
||||
}
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
final Tags eagleTim = Tags.createAndAddToDictionary("bird", "eagle", "name", "Tim");
|
||||
final long eagleTimBlockOffset = dataStore.createNewFile(new ParititionId("partitionA"), eagleTim);
|
||||
Assert.assertEquals(eagleTimBlockOffset % BSFile.BLOCK_SIZE, 0);
|
||||
}
|
||||
|
||||
@DataProvider(name = "providerProposals")
|
||||
public Iterator<Object[]> providerProposals() {
|
||||
@DataProvider(name = "providerProposals")
|
||||
public Iterator<Object[]> providerProposals() {
|
||||
|
||||
final List<Object[]> result = new ArrayList<>();
|
||||
final List<Object[]> result = new ArrayList<>();
|
||||
|
||||
result.add(new Object[] { "type=bird and subtype=eagle and name=|", "name", Arrays.asList("Tim") });
|
||||
result.add(new Object[] { "type=bird and subtype=eagle and name=|", "name", Arrays.asList("Tim") });
|
||||
|
||||
// returns Tim, because it is the only dog's name starting with 'Ti'
|
||||
result.add(new Object[] { "!name=Ti| and type=dog", "name", Arrays.asList("Tim") });
|
||||
// returns Tim, because it is the only dog's name starting with 'Ti'
|
||||
result.add(new Object[] { "!name=Ti| and type=dog", "name", Arrays.asList("Tim") });
|
||||
|
||||
// all cats
|
||||
result.add(new Object[] { "type=cat and !name=|", "name",
|
||||
Arrays.asList("Jane", "John", "Paul", "Sam", "Timothy") });
|
||||
// all cats
|
||||
result.add(new Object[] { "type=cat and !name=|", "name",
|
||||
Arrays.asList("Jane", "John", "Paul", "Sam", "Timothy") });
|
||||
|
||||
// finds nothing, because there are not dogs names neither Jenny, nor Ti*
|
||||
result.add(new Object[] { "!name=Ti| and type=dog and !name=Jenny", "name", Arrays.asList() });
|
||||
// finds nothing, because there are not dogs names neither Jenny, nor Ti*
|
||||
result.add(new Object[] { "!name=Ti| and type=dog and !name=Jenny", "name", Arrays.asList() });
|
||||
|
||||
result.add(new Object[] { "(type=bird and age=three or type=dog and age=three) and name=|", "name",
|
||||
Arrays.asList("Jenny", "Tim") });
|
||||
result.add(new Object[] { "(type=bird and age=three or type=dog and age=three) and name=|", "name",
|
||||
Arrays.asList("Jenny", "Tim") });
|
||||
|
||||
// all but Jennifer
|
||||
result.add(new Object[] { "!(type=bird) and name=|", "name",
|
||||
Arrays.asList("Jane", "Jenny", "John", "Paul", "Sam", "Tim", "Timothy") });
|
||||
// all but Jennifer
|
||||
result.add(new Object[] { "!(type=bird) and name=|", "name",
|
||||
Arrays.asList("Jane", "Jenny", "John", "Paul", "Sam", "Tim", "Timothy") });
|
||||
|
||||
result.add(new Object[] { "type=bird and !subtype=eagle and name=|", "name", Arrays.asList("Jennifer") });
|
||||
result.add(new Object[] { "type=bird and !subtype=eagle and name=|", "name", Arrays.asList("Jennifer") });
|
||||
|
||||
// DeMorgan
|
||||
// TODO should only match "Jenny", because Jenny is the only non-bird name
|
||||
// starting with 'Jen'
|
||||
result.add(new Object[] { "!(type=bird and name=Jen|)", "name", Arrays.asList("Jennifer", "Jenny") });
|
||||
// DeMorgan
|
||||
// TODO should only match "Jenny", because Jenny is the only non-bird name
|
||||
// starting with 'Jen'
|
||||
result.add(new Object[] { "!(type=bird and name=Jen|)", "name", Arrays.asList("Jennifer", "Jenny") });
|
||||
|
||||
result.add(new Object[] { "!(type=dog and name=|) and !type=cat", "name",
|
||||
Arrays.asList("Jennifer", "Jenny", "Tim") });
|
||||
result.add(new Object[] { "!(type=dog and name=|) and !type=cat", "name",
|
||||
Arrays.asList("Jennifer", "Jenny", "Tim") });
|
||||
|
||||
// not existing field
|
||||
result.add(new Object[] { "name=| and XYZ=Tim", "name", Arrays.asList() });
|
||||
// not existing field
|
||||
result.add(new Object[] { "name=| and XYZ=Tim", "name", Arrays.asList() });
|
||||
|
||||
// not existing value
|
||||
result.add(new Object[] { "name=| and type=XYZ", "name", Arrays.asList() });
|
||||
// not existing value
|
||||
result.add(new Object[] { "name=| and type=XYZ", "name", Arrays.asList() });
|
||||
|
||||
return result.iterator();
|
||||
}
|
||||
return result.iterator();
|
||||
}
|
||||
|
||||
@Test(dataProvider = "providerProposals")
|
||||
public void testProposals(final String queryWithCaret, final String field,
|
||||
final List<String> expectedProposedValues) throws Exception {
|
||||
@Test(dataProvider = "providerProposals")
|
||||
public void testProposals(final String queryWithCaret, final String field,
|
||||
final List<String> expectedProposedValues) throws Exception {
|
||||
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
final ParititionId partitionId = DateIndexExtension.now();
|
||||
final DateTimeRange dateRange = DateTimeRange.relativeHours(1);
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
final ParititionId partitionId = DateIndexExtension.now();
|
||||
final DateTimeRange dateRange = DateTimeRange.relativeHours(1);
|
||||
|
||||
final List<Tags> tags = Arrays.asList(
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "eagle", "age", "three", "name", "Tim"),
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "pigeon", "age", "two", "name", "Jennifer"),
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "flamingo", "age", "one", "name", "Jennifer"),
|
||||
final List<Tags> tags = Arrays.asList(
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "eagle", "age", "three", "name", "Tim"),
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "pigeon", "age", "two", "name", "Jennifer"),
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "flamingo", "age", "one", "name", "Jennifer"),
|
||||
|
||||
Tags.createAndAddToDictionary("type", "dog", "subtype", "labrador", "age", "three", "name", "Jenny"),
|
||||
Tags.createAndAddToDictionary("type", "dog", "subtype", "labrador", "age", "three", "name", "Tim"),
|
||||
Tags.createAndAddToDictionary("type", "dog", "subtype", "labrador", "age", "three", "name", "Jenny"),
|
||||
Tags.createAndAddToDictionary("type", "dog", "subtype", "labrador", "age", "three", "name", "Tim"),
|
||||
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "tiger", "age", "one", "name", "Timothy"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "tiger", "age", "two", "name", "Paul"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "three", "name", "Jane"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "four", "name", "Sam"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "four", "name", "John"));
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "tiger", "age", "one", "name", "Timothy"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "tiger", "age", "two", "name", "Paul"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "three", "name", "Jane"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "four", "name", "Sam"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "four", "name", "John"));
|
||||
|
||||
tags.forEach(t -> dataStore.createNewFile(partitionId, t));
|
||||
tags.forEach(t -> dataStore.createNewFile(partitionId, t));
|
||||
|
||||
assertProposals(dateRange, queryWithCaret, field, expectedProposedValues);
|
||||
}
|
||||
assertProposals(dateRange, queryWithCaret, field, expectedProposedValues);
|
||||
}
|
||||
|
||||
public void testIdenticalDatesGoIntoSameFile() throws Exception {
|
||||
public void testIdenticalDatesGoIntoSameFile() throws Exception {
|
||||
|
||||
try (final DataStore dataStore = new DataStore(dataDirectory)) {
|
||||
try (final DataStore dataStore = new DataStore(dataDirectory)) {
|
||||
|
||||
final long timestamp = DateUtils.getDate(2016, 1, 1, 13, 1, 1).toInstant().toEpochMilli();
|
||||
final long timestamp = DateUtils.getDate(2016, 1, 1, 13, 1, 1).toInstant().toEpochMilli();
|
||||
|
||||
final Tags tags = Tags.createAndAddToDictionary("myKey", "myValue");
|
||||
final Tags tags = Tags.createAndAddToDictionary("myKey", "myValue");
|
||||
|
||||
dataStore.write(timestamp, tags, 1);
|
||||
dataStore.write(timestamp, tags, 2);
|
||||
dataStore.write(timestamp, tags, 1);
|
||||
dataStore.write(timestamp, tags, 2);
|
||||
|
||||
Assert.assertEquals(dataStore.sizeWriterCache(), 1, "size of the writer cache");
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(dataStore.sizeWriterCache(), 1, "size of the writer cache");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(final String[] args) throws IOException, InterruptedException {
|
||||
final Path dir = Files.createTempDirectory("pdb");
|
||||
try (final DataStore dataStore = new DataStore(dir)) {
|
||||
public static void main(final String[] args) throws IOException, InterruptedException {
|
||||
final Path dir = Files.createTempDirectory("pdb");
|
||||
try (final DataStore dataStore = new DataStore(dir)) {
|
||||
|
||||
final List<Tags> tags = Arrays.asList(
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "eagle", "age", "three", "name", "Tim"),
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "pigeon", "age", "two", "name",
|
||||
"Jennifer"),
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "flamingo", "age", "one", "name",
|
||||
"Jennifer"),
|
||||
final List<Tags> tags = Arrays.asList(
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "eagle", "age", "three", "name", "Tim"),
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "pigeon", "age", "two", "name",
|
||||
"Jennifer"),
|
||||
Tags.createAndAddToDictionary("type", "bird", "subtype", "flamingo", "age", "one", "name",
|
||||
"Jennifer"),
|
||||
|
||||
Tags.createAndAddToDictionary("type", "dog", "subtype", "labrador", "age", "three", "name",
|
||||
"Jenny"),
|
||||
Tags.createAndAddToDictionary("type", "dog", "subtype", "labrador", "age", "three", "name", "Tim"),
|
||||
Tags.createAndAddToDictionary("type", "dog", "subtype", "labrador", "age", "three", "name",
|
||||
"Jenny"),
|
||||
Tags.createAndAddToDictionary("type", "dog", "subtype", "labrador", "age", "three", "name", "Tim"),
|
||||
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "tiger", "age", "one", "name", "Timothy"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "tiger", "age", "two", "name", "Paul"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "three", "name", "Jane"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "four", "name", "Sam"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "four", "name", "John"));
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "tiger", "age", "one", "name", "Timothy"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "tiger", "age", "two", "name", "Paul"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "three", "name", "Jane"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "four", "name", "Sam"),
|
||||
Tags.createAndAddToDictionary("type", "cat", "subtype", "lion", "age", "four", "name", "John"));
|
||||
|
||||
final DateTimeRange dateRange = DateTimeRange.relativeMillis(0);
|
||||
final ParititionId partitionId = DateIndexExtension.toPartitionIds(dateRange).get(0);
|
||||
tags.forEach(t -> dataStore.createNewFile(partitionId, t));
|
||||
final DateTimeRange dateRange = DateTimeRange.relativeMillis(0);
|
||||
final ParititionId partitionId = DateIndexExtension.toPartitionIds(dateRange).get(0);
|
||||
tags.forEach(t -> dataStore.createNewFile(partitionId, t));
|
||||
|
||||
final JFrame frame = new JFrame();
|
||||
final JTextField input = new JTextField();
|
||||
final JTextArea output = new JTextArea();
|
||||
final JTextArea info = new JTextArea();
|
||||
final JFrame frame = new JFrame();
|
||||
final JTextField input = new JTextField();
|
||||
final JTextArea output = new JTextArea();
|
||||
final JTextArea info = new JTextArea();
|
||||
|
||||
frame.add(input, BorderLayout.NORTH);
|
||||
frame.add(output, BorderLayout.CENTER);
|
||||
frame.add(info, BorderLayout.SOUTH);
|
||||
frame.add(input, BorderLayout.NORTH);
|
||||
frame.add(output, BorderLayout.CENTER);
|
||||
frame.add(info, BorderLayout.SOUTH);
|
||||
|
||||
input.setText("type=bird and !subtype=eagle and name=");
|
||||
input.setText("type=bird and !subtype=eagle and name=");
|
||||
|
||||
input.addKeyListener(new KeyAdapter() {
|
||||
input.addKeyListener(new KeyAdapter() {
|
||||
|
||||
@Override
|
||||
public void keyReleased(final KeyEvent e) {
|
||||
@Override
|
||||
public void keyReleased(final KeyEvent e) {
|
||||
|
||||
final String query = input.getText();
|
||||
final int caretIndex = input.getCaretPosition();
|
||||
final QueryWithCaretMarker q = new QueryWithCaretMarker(query, dateRange, caretIndex,
|
||||
ResultMode.CUT_AT_DOT);
|
||||
final String query = input.getText();
|
||||
final int caretIndex = input.getCaretPosition();
|
||||
final QueryWithCaretMarker q = new QueryWithCaretMarker(query, dateRange, caretIndex,
|
||||
ResultMode.CUT_AT_DOT);
|
||||
|
||||
final List<Proposal> proposals = dataStore.propose(q);
|
||||
final List<Proposal> proposals = dataStore.propose(q);
|
||||
|
||||
final StringBuilder out = new StringBuilder();
|
||||
final StringBuilder out = new StringBuilder();
|
||||
|
||||
for (final Proposal proposal : proposals) {
|
||||
out.append(proposal.getProposedTag());
|
||||
out.append(" ");
|
||||
out.append(proposal.getProposedQuery());
|
||||
out.append("\n");
|
||||
}
|
||||
for (final Proposal proposal : proposals) {
|
||||
out.append(proposal.getProposedTag());
|
||||
out.append(" ");
|
||||
out.append(proposal.getProposedQuery());
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
final String queryWithCaretMarker = new StringBuilder(query).insert(caretIndex, "|").toString();
|
||||
final String queryWithCaretMarker = new StringBuilder(query).insert(caretIndex, "|").toString();
|
||||
|
||||
out.append("\n");
|
||||
out.append("\n");
|
||||
out.append("input: " + queryWithCaretMarker);
|
||||
out.append("\n");
|
||||
out.append("\n");
|
||||
out.append("input: " + queryWithCaretMarker);
|
||||
|
||||
output.setText(out.toString());
|
||||
output.setText(out.toString());
|
||||
|
||||
}
|
||||
});
|
||||
final List<Doc> docs = dataStore.search(Query.createQuery("", DateTimeRange.relative(1, ChronoUnit.DAYS)));
|
||||
final StringBuilder out = new StringBuilder();
|
||||
out.append("info\n");
|
||||
for (final Doc doc : docs) {
|
||||
out.append(doc.getTags());
|
||||
out.append("\n");
|
||||
}
|
||||
info.setText(out.toString());
|
||||
}
|
||||
});
|
||||
final List<Doc> docs = dataStore.search(Query.createQuery("", DateTimeRange.relative(1, ChronoUnit.DAYS)));
|
||||
final StringBuilder out = new StringBuilder();
|
||||
out.append("info\n");
|
||||
for (final Doc doc : docs) {
|
||||
out.append(doc.getTags());
|
||||
out.append("\n");
|
||||
}
|
||||
info.setText(out.toString());
|
||||
|
||||
frame.setSize(800, 600);
|
||||
frame.setVisible(true);
|
||||
TimeUnit.HOURS.sleep(1000);
|
||||
}
|
||||
}
|
||||
frame.setSize(800, 600);
|
||||
frame.setVisible(true);
|
||||
TimeUnit.HOURS.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertProposals(final DateTimeRange dateRange, final String queryWithCaret, final String field,
|
||||
final List<String> expectedProposedValues) {
|
||||
final String query = queryWithCaret.replace("|", "");
|
||||
final int caretIndex = queryWithCaret.indexOf("|");
|
||||
final List<Proposal> proposals = dataStore
|
||||
.propose(new QueryWithCaretMarker(query, dateRange, caretIndex, ResultMode.CUT_AT_DOT));
|
||||
System.out.println(
|
||||
"proposed values: " + proposals.stream().map(Proposal::getProposedTag).collect(Collectors.toList()));
|
||||
|
||||
proposals.forEach(p -> assertQueryFindsResults(dateRange, p.getNewQuery()));
|
||||
|
||||
final List<String> proposedValues = CollectionUtils.map(proposals, Proposal::getProposedTag);
|
||||
Collections.sort(proposedValues);
|
||||
Collections.sort(expectedProposedValues);
|
||||
Assert.assertEquals(proposedValues.toString(), expectedProposedValues.toString(), "proposed values:");
|
||||
}
|
||||
|
||||
private void assertQueryFindsResults(final DateTimeRange dateRange, final String query) {
|
||||
final List<Doc> result = dataStore.search(new Query(query, dateRange));
|
||||
Assert.assertFalse(result.isEmpty(), "The query '" + query + "' must return a result, but didn't.");
|
||||
}
|
||||
|
||||
private void assertSearch(final DateTimeRange dateRange, final String queryString, final Tags... tags) {
|
||||
final Query query = new Query(queryString, dateRange);
|
||||
final List<Doc> actualDocs = dataStore.search(query);
|
||||
final List<Long> actual = CollectionUtils.map(actualDocs, Doc::getRootBlockNumber);
|
||||
|
||||
final List<Long> expectedPaths = CollectionUtils.map(tags, tagsToBlockStorageRootBlockNumber::get);
|
||||
|
||||
Assert.assertEquals(actual, expectedPaths, "Query: " + queryString + " Found: " + actual);
|
||||
}
|
||||
private void assertProposals(final DateTimeRange dateRange, final String queryWithCaret, final String field,
|
||||
final List<String> expectedProposedValues) {
|
||||
final String query = queryWithCaret.replace("|", "");
|
||||
final int caretIndex = queryWithCaret.indexOf("|");
|
||||
final List<Proposal> proposals = dataStore
|
||||
.propose(new QueryWithCaretMarker(query, dateRange, caretIndex, ResultMode.CUT_AT_DOT));
|
||||
System.out.println(
|
||||
"proposed values: " + proposals.stream().map(Proposal::getProposedTag).collect(Collectors.toList()));
|
||||
|
||||
proposals.forEach(p -> assertQueryFindsResults(dateRange, p.getNewQuery()));
|
||||
|
||||
final List<String> proposedValues = CollectionUtils.map(proposals, Proposal::getProposedTag);
|
||||
Collections.sort(proposedValues);
|
||||
Collections.sort(expectedProposedValues);
|
||||
Assert.assertEquals(proposedValues.toString(), expectedProposedValues.toString(), "proposed values:");
|
||||
}
|
||||
|
||||
private void assertQueryFindsResults(final DateTimeRange dateRange, final String query) {
|
||||
final List<Doc> result = dataStore.search(new Query(query, dateRange));
|
||||
Assert.assertFalse(result.isEmpty(), "The query '" + query + "' must return a result, but didn't.");
|
||||
}
|
||||
|
||||
private void assertSearch(final DateTimeRange dateRange, final String queryString, final Tags... tags) {
|
||||
final Query query = new Query(queryString, dateRange);
|
||||
final List<Doc> actualDocs = dataStore.search(query);
|
||||
final List<Long> actual = CollectionUtils.map(actualDocs, Doc::getRootBlockNumber);
|
||||
|
||||
final List<Long> expectedPaths = CollectionUtils.map(tags, tagsToBlockStorageRootBlockNumber::get);
|
||||
|
||||
Assert.assertEquals(actual, expectedPaths, "Query: " + queryString + " Found: " + actual);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,129 +16,129 @@ import org.testng.annotations.Test;
|
||||
@Test
|
||||
public class DateIndexExtensionTest {
|
||||
|
||||
@DataProvider
|
||||
public Object[][] provider() {
|
||||
@DataProvider
|
||||
public Object[][] provider() {
|
||||
|
||||
final List<Object[]> result = new ArrayList<>();
|
||||
final List<Object[]> result = new ArrayList<>();
|
||||
|
||||
{
|
||||
final OffsetDateTime start = OffsetDateTime.of(2018, 1, 31, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final OffsetDateTime end = OffsetDateTime.of(2018, 1, 31, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final Set<String> expected = Set.of("201801");
|
||||
result.add(new Object[] { start, end, expected });
|
||||
}
|
||||
{
|
||||
final OffsetDateTime start = OffsetDateTime.of(2017, 11, 1, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final OffsetDateTime end = OffsetDateTime.of(2018, 02, 1, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final Set<String> expected = Set.of("201711", "201712", "201801", "201802");
|
||||
result.add(new Object[] { start, end, expected });
|
||||
}
|
||||
{
|
||||
// check that adding one month to Jan 31 does not skip the February
|
||||
final OffsetDateTime start = OffsetDateTime.of(2018, 1, 31, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final OffsetDateTime end = OffsetDateTime.of(2018, 3, 31, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final Set<String> expected = Set.of("201801", "201802", "201803");
|
||||
result.add(new Object[] { start, end, expected });
|
||||
}
|
||||
{
|
||||
final OffsetDateTime start = OffsetDateTime.of(2018, 1, 31, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final OffsetDateTime end = OffsetDateTime.of(2018, 1, 31, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final Set<String> expected = Set.of("201801");
|
||||
result.add(new Object[] { start, end, expected });
|
||||
}
|
||||
{
|
||||
final OffsetDateTime start = OffsetDateTime.of(2017, 11, 1, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final OffsetDateTime end = OffsetDateTime.of(2018, 02, 1, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final Set<String> expected = Set.of("201711", "201712", "201801", "201802");
|
||||
result.add(new Object[] { start, end, expected });
|
||||
}
|
||||
{
|
||||
// check that adding one month to Jan 31 does not skip the February
|
||||
final OffsetDateTime start = OffsetDateTime.of(2018, 1, 31, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final OffsetDateTime end = OffsetDateTime.of(2018, 3, 31, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
final Set<String> expected = Set.of("201801", "201802", "201803");
|
||||
result.add(new Object[] { start, end, expected });
|
||||
}
|
||||
|
||||
return result.toArray(new Object[0][]);
|
||||
}
|
||||
return result.toArray(new Object[0][]);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "provider")
|
||||
public void test(final OffsetDateTime start, final OffsetDateTime end, final Set<String> expected) {
|
||||
@Test(dataProvider = "provider")
|
||||
public void test(final OffsetDateTime start, final OffsetDateTime end, final Set<String> expected) {
|
||||
|
||||
final DateTimeRange dateRange = new DateTimeRange(start, end);
|
||||
final DateTimeRange dateRange = new DateTimeRange(start, end);
|
||||
|
||||
final Set<String> actual = DateIndexExtension.toDateIndexPrefix(dateRange);
|
||||
final Set<String> actual = DateIndexExtension.toDateIndexPrefix(dateRange);
|
||||
|
||||
Assert.assertEquals(actual, expected);
|
||||
}
|
||||
Assert.assertEquals(actual, expected);
|
||||
}
|
||||
|
||||
public void testDateToDateIndexPrefix() {
|
||||
public void testDateToDateIndexPrefix() {
|
||||
|
||||
final long mid_201711 = OffsetDateTime.of(2017, 11, 23, 2, 2, 2, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long mid_201712 = OffsetDateTime.of(2017, 12, 7, 1, 1, 1, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long min_201801 = OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long max_201801 = OffsetDateTime.of(2018, 1, 31, 23, 59, 59, 999_999_999, ZoneOffset.UTC).toInstant()
|
||||
.toEpochMilli();
|
||||
final long mid_201711 = OffsetDateTime.of(2017, 11, 23, 2, 2, 2, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long mid_201712 = OffsetDateTime.of(2017, 12, 7, 1, 1, 1, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long min_201801 = OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long max_201801 = OffsetDateTime.of(2018, 1, 31, 23, 59, 59, 999_999_999, ZoneOffset.UTC).toInstant()
|
||||
.toEpochMilli();
|
||||
|
||||
Assert.assertEquals(DateIndexExtension.toDateIndexPrefix(mid_201712), "201712");
|
||||
Assert.assertEquals(DateIndexExtension.toDateIndexPrefix(min_201801), "201801");
|
||||
Assert.assertEquals(DateIndexExtension.toDateIndexPrefix(max_201801), "201801");
|
||||
Assert.assertEquals(DateIndexExtension.toDateIndexPrefix(mid_201711), "201711");
|
||||
}
|
||||
Assert.assertEquals(DateIndexExtension.toDateIndexPrefix(mid_201712), "201712");
|
||||
Assert.assertEquals(DateIndexExtension.toDateIndexPrefix(min_201801), "201801");
|
||||
Assert.assertEquals(DateIndexExtension.toDateIndexPrefix(max_201801), "201801");
|
||||
Assert.assertEquals(DateIndexExtension.toDateIndexPrefix(mid_201711), "201711");
|
||||
}
|
||||
|
||||
public void testDateRanges() {
|
||||
final OffsetDateTime mid_201712 = OffsetDateTime.of(2017, 12, 7, 1, 1, 1, 0, ZoneOffset.UTC)
|
||||
.withOffsetSameInstant(ZoneOffset.ofHours(-2));
|
||||
final OffsetDateTime min_201801 = OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)
|
||||
.withOffsetSameInstant(ZoneOffset.ofHours(-8));
|
||||
final OffsetDateTime min_201802 = OffsetDateTime.of(2018, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC)
|
||||
.withOffsetSameInstant(ZoneOffset.ofHours(12));
|
||||
public void testDateRanges() {
|
||||
final OffsetDateTime mid_201712 = OffsetDateTime.of(2017, 12, 7, 1, 1, 1, 0, ZoneOffset.UTC)
|
||||
.withOffsetSameInstant(ZoneOffset.ofHours(-2));
|
||||
final OffsetDateTime min_201801 = OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)
|
||||
.withOffsetSameInstant(ZoneOffset.ofHours(-8));
|
||||
final OffsetDateTime min_201802 = OffsetDateTime.of(2018, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC)
|
||||
.withOffsetSameInstant(ZoneOffset.ofHours(12));
|
||||
|
||||
final DateTimeRange range_201712_201802 = new DateTimeRange(mid_201712, min_201802);
|
||||
final DateTimeRange range_201712_201801 = new DateTimeRange(mid_201712, min_201801);
|
||||
final DateTimeRange range_201712_201712 = new DateTimeRange(mid_201712, mid_201712);
|
||||
final DateTimeRange range_201712_201802 = new DateTimeRange(mid_201712, min_201802);
|
||||
final DateTimeRange range_201712_201801 = new DateTimeRange(mid_201712, min_201801);
|
||||
final DateTimeRange range_201712_201712 = new DateTimeRange(mid_201712, mid_201712);
|
||||
|
||||
final List<ParititionId> dateIndexPrefixesWithEmptyCache = DateIndexExtension
|
||||
.toPartitionIds(range_201712_201802);
|
||||
Assert.assertEquals(dateIndexPrefixesWithEmptyCache,
|
||||
Arrays.asList(new ParititionId("201712"), new ParititionId("201801"), new ParititionId("201802")));
|
||||
final List<ParititionId> dateIndexPrefixesWithEmptyCache = DateIndexExtension
|
||||
.toPartitionIds(range_201712_201802);
|
||||
Assert.assertEquals(dateIndexPrefixesWithEmptyCache,
|
||||
Arrays.asList(new ParititionId("201712"), new ParititionId("201801"), new ParititionId("201802")));
|
||||
|
||||
final List<ParititionId> dateIndexPrefixesWithFilledCache = DateIndexExtension
|
||||
.toPartitionIds(range_201712_201801);
|
||||
Assert.assertEquals(dateIndexPrefixesWithFilledCache,
|
||||
Arrays.asList(new ParititionId("201712"), new ParititionId("201801")));
|
||||
final List<ParititionId> dateIndexPrefixesWithFilledCache = DateIndexExtension
|
||||
.toPartitionIds(range_201712_201801);
|
||||
Assert.assertEquals(dateIndexPrefixesWithFilledCache,
|
||||
Arrays.asList(new ParititionId("201712"), new ParititionId("201801")));
|
||||
|
||||
final List<ParititionId> dateIndexPrefixesOneMonth = DateIndexExtension.toPartitionIds(range_201712_201712);
|
||||
Assert.assertEquals(dateIndexPrefixesOneMonth, Arrays.asList(new ParititionId("201712")));
|
||||
}
|
||||
final List<ParititionId> dateIndexPrefixesOneMonth = DateIndexExtension.toPartitionIds(range_201712_201712);
|
||||
Assert.assertEquals(dateIndexPrefixesOneMonth, Arrays.asList(new ParititionId("201712")));
|
||||
}
|
||||
|
||||
public void testDateRangeToEpochMilli() {
|
||||
final OffsetDateTime mid_201712 = OffsetDateTime.of(2017, 12, 7, 1, 1, 1, 0, ZoneOffset.ofHours(3));
|
||||
final OffsetDateTime min_201802 = OffsetDateTime.of(2018, 2, 15, 0, 0, 0, 0, ZoneOffset.ofHours(7));
|
||||
public void testDateRangeToEpochMilli() {
|
||||
final OffsetDateTime mid_201712 = OffsetDateTime.of(2017, 12, 7, 1, 1, 1, 0, ZoneOffset.ofHours(3));
|
||||
final OffsetDateTime min_201802 = OffsetDateTime.of(2018, 2, 15, 0, 0, 0, 0, ZoneOffset.ofHours(7));
|
||||
|
||||
final long exp_201712 = OffsetDateTime.of(2017, 12, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long exp_201801 = OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long exp_201802 = OffsetDateTime.of(2018, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long exp_201712 = OffsetDateTime.of(2017, 12, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long exp_201801 = OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long exp_201802 = OffsetDateTime.of(2018, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
|
||||
final List<Long> dateIndexEpochMillis = DateIndexExtension
|
||||
.toDateIndexEpochMillis(new DateTimeRange(mid_201712, min_201802));
|
||||
Assert.assertEquals(dateIndexEpochMillis, Arrays.asList(exp_201712, exp_201801, exp_201802));
|
||||
}
|
||||
final List<Long> dateIndexEpochMillis = DateIndexExtension
|
||||
.toDateIndexEpochMillis(new DateTimeRange(mid_201712, min_201802));
|
||||
Assert.assertEquals(dateIndexEpochMillis, Arrays.asList(exp_201712, exp_201801, exp_201802));
|
||||
}
|
||||
|
||||
public void testPerformance() {
|
||||
public void testPerformance() {
|
||||
|
||||
final long min = OffsetDateTime.of(2010, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long mid = OffsetDateTime.of(2020, 6, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long max = OffsetDateTime.of(2030, 12, 31, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long min = OffsetDateTime.of(2010, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long mid = OffsetDateTime.of(2020, 6, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
final long max = OffsetDateTime.of(2030, 12, 31, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
|
||||
final int iterations = 1_000_000;
|
||||
final int factor = 1;
|
||||
final int warmup = 20 * factor;
|
||||
final int rounds = warmup + 20;
|
||||
final int iterations = 1_000_000;
|
||||
final int factor = 1;
|
||||
final int warmup = 20 * factor;
|
||||
final int rounds = warmup + 20;
|
||||
|
||||
// fill the cache
|
||||
DateIndexExtension.DATE_PREFIX_CACHE.clear();
|
||||
for (long i = min; i < max; i += 3600 * 24 * 28) {
|
||||
DateIndexExtension.toPartitionId(i);
|
||||
}
|
||||
// fill the cache
|
||||
DateIndexExtension.DATE_PREFIX_CACHE.clear();
|
||||
for (long i = min; i < max; i += 3600 * 24 * 28) {
|
||||
DateIndexExtension.toPartitionId(i);
|
||||
}
|
||||
|
||||
final List<Double> measurements = new ArrayList<>();
|
||||
final List<Double> measurements = new ArrayList<>();
|
||||
|
||||
for (int r = 0; r < rounds; r++) {
|
||||
for (int r = 0; r < rounds; r++) {
|
||||
|
||||
final long start = System.nanoTime();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
DateIndexExtension.toPartitionId(mid);
|
||||
}
|
||||
final double duration = (System.nanoTime() - start) / 1_000_000.0;
|
||||
System.out.println("duration: " + duration + "ms");
|
||||
measurements.add(duration);
|
||||
}
|
||||
final long start = System.nanoTime();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
DateIndexExtension.toPartitionId(mid);
|
||||
}
|
||||
final double duration = (System.nanoTime() - start) / 1_000_000.0;
|
||||
System.out.println("duration: " + duration + "ms");
|
||||
measurements.add(duration);
|
||||
}
|
||||
|
||||
final DoubleSummaryStatistics stats = measurements.subList(warmup, rounds).stream().mapToDouble(d -> factor * d)
|
||||
.summaryStatistics();
|
||||
System.out.println(stats);
|
||||
}
|
||||
final DoubleSummaryStatistics stats = measurements.subList(warmup, rounds).stream().mapToDouble(d -> factor * d)
|
||||
.summaryStatistics();
|
||||
System.out.println(stats);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,275 +22,276 @@ import org.testng.annotations.Test;
|
||||
@Test
|
||||
public class ProposerTest {
|
||||
|
||||
private Path dataDirectory;
|
||||
private DataStore dataStore;
|
||||
private DateTimeRange dateRange;
|
||||
private Path dataDirectory;
|
||||
private DataStore dataStore;
|
||||
private DateTimeRange dateRange;
|
||||
|
||||
@BeforeClass
|
||||
public void beforeClass() throws Exception {
|
||||
dataDirectory = Files.createTempDirectory("pdb");
|
||||
initDatabase();
|
||||
}
|
||||
@BeforeClass
|
||||
public void beforeClass() throws Exception {
|
||||
dataDirectory = Files.createTempDirectory("pdb");
|
||||
initDatabase();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void afterClass() throws IOException {
|
||||
FileUtils.delete(dataDirectory);
|
||||
dataStore.close();
|
||||
dataStore = null;
|
||||
Tags.STRING_COMPRESSOR = null;
|
||||
}
|
||||
@AfterClass
|
||||
public void afterClass() throws IOException {
|
||||
FileUtils.delete(dataDirectory);
|
||||
dataStore.close();
|
||||
dataStore = null;
|
||||
Tags.STRING_COMPRESSOR = null;
|
||||
}
|
||||
|
||||
private void initDatabase() throws Exception {
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
dateRange = DateTimeRange.now();
|
||||
final ParititionId now = DateIndexExtension.toPartitionIds(dateRange).get(0);
|
||||
private void initDatabase() throws Exception {
|
||||
dataStore = new DataStore(dataDirectory);
|
||||
dateRange = DateTimeRange.now();
|
||||
final ParititionId now = DateIndexExtension.toPartitionIds(dateRange).get(0);
|
||||
|
||||
final Tags eagleTim = Tags.createAndAddToDictionary("bird", "eagle", "name", "Tim");
|
||||
final Tags eagleTimothy = Tags.createAndAddToDictionary("bird", "eagle", "name", "Timothy");
|
||||
final Tags pigeonJennifer = Tags.createAndAddToDictionary("bird", "pigeon", "name", "Jennifer");
|
||||
final Tags flamingoJennifer = Tags.createAndAddToDictionary("bird", "flamingo", "name", "Jennifer");
|
||||
final Tags labradorJenny = Tags.createAndAddToDictionary("dog", "labrador", "name", "Jenny");
|
||||
final Tags labradorTim = Tags.createAndAddToDictionary("dog", "labrador", "name", "Tim");
|
||||
final Tags eagleTim = Tags.createAndAddToDictionary("bird", "eagle", "name", "Tim");
|
||||
final Tags eagleTimothy = Tags.createAndAddToDictionary("bird", "eagle", "name", "Timothy");
|
||||
final Tags pigeonJennifer = Tags.createAndAddToDictionary("bird", "pigeon", "name", "Jennifer");
|
||||
final Tags flamingoJennifer = Tags.createAndAddToDictionary("bird", "flamingo", "name", "Jennifer");
|
||||
final Tags labradorJenny = Tags.createAndAddToDictionary("dog", "labrador", "name", "Jenny");
|
||||
final Tags labradorTim = Tags.createAndAddToDictionary("dog", "labrador", "name", "Tim");
|
||||
|
||||
final Tags methodA = Tags.createAndAddToDictionary("method", "FooController.doImportantStuff", "source", "web");
|
||||
final Tags methodB = Tags.createAndAddToDictionary("method", "FooService.doImportantStuff", "source",
|
||||
"service");
|
||||
final Tags methodC = Tags.createAndAddToDictionary("method", "BarController.doBoringStuff", "source", "web");
|
||||
final Tags methodD = Tags.createAndAddToDictionary("method", "FooBarService.doOtherStuff", "source", "service");
|
||||
final Tags methodA = Tags.createAndAddToDictionary("method", "FooController.doImportantStuff", "source", "web");
|
||||
final Tags methodB = Tags.createAndAddToDictionary("method", "FooService.doImportantStuff", "source",
|
||||
"service");
|
||||
final Tags methodC = Tags.createAndAddToDictionary("method", "BarController.doBoringStuff", "source", "web");
|
||||
final Tags methodD = Tags.createAndAddToDictionary("method", "FooBarService.doOtherStuff", "source", "service");
|
||||
|
||||
dataStore.createNewFile(now, eagleTim);
|
||||
dataStore.createNewFile(now, eagleTimothy);
|
||||
dataStore.createNewFile(now, pigeonJennifer);
|
||||
dataStore.createNewFile(now, flamingoJennifer);
|
||||
dataStore.createNewFile(now, labradorJenny);
|
||||
dataStore.createNewFile(now, labradorTim);
|
||||
dataStore.createNewFile(now, eagleTim);
|
||||
dataStore.createNewFile(now, eagleTimothy);
|
||||
dataStore.createNewFile(now, pigeonJennifer);
|
||||
dataStore.createNewFile(now, flamingoJennifer);
|
||||
dataStore.createNewFile(now, labradorJenny);
|
||||
dataStore.createNewFile(now, labradorTim);
|
||||
|
||||
dataStore.createNewFile(now, methodA);
|
||||
dataStore.createNewFile(now, methodB);
|
||||
dataStore.createNewFile(now, methodC);
|
||||
dataStore.createNewFile(now, methodD);
|
||||
}
|
||||
dataStore.createNewFile(now, methodA);
|
||||
dataStore.createNewFile(now, methodB);
|
||||
dataStore.createNewFile(now, methodC);
|
||||
dataStore.createNewFile(now, methodD);
|
||||
}
|
||||
|
||||
public void testEmptyQuery() throws Exception {
|
||||
public void testEmptyQuery() throws Exception {
|
||||
|
||||
assertProposals("|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("name", "name=*", true, "name=", 5), //
|
||||
new Proposal("bird", "bird=*", true, "bird=", 5), //
|
||||
new Proposal("dog", "dog=*", true, "dog=", 4), //
|
||||
new Proposal("method", "method=*", true, "method=", 7), //
|
||||
new Proposal("source", "source=*", true, "source=", 7)//
|
||||
);
|
||||
assertProposals("|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("name", "name=*", true, "name=", 5), //
|
||||
new Proposal("bird", "bird=*", true, "bird=", 5), //
|
||||
new Proposal("dog", "dog=*", true, "dog=", 4), //
|
||||
new Proposal("method", "method=*", true, "method=", 7), //
|
||||
new Proposal("source", "source=*", true, "source=", 7)//
|
||||
);
|
||||
|
||||
assertProposals(" |", ResultMode.FULL_VALUES, //
|
||||
new Proposal("name", "name=*", true, "name=", 5), //
|
||||
new Proposal("bird", "bird=*", true, "bird=", 5), //
|
||||
new Proposal("dog", "dog=*", true, "dog=", 4), //
|
||||
new Proposal("method", "method=*", true, "method=", 7), //
|
||||
new Proposal("source", "source=*", true, "source=", 7)//
|
||||
);
|
||||
}
|
||||
assertProposals(" |", ResultMode.FULL_VALUES, //
|
||||
new Proposal("name", "name=*", true, "name=", 5), //
|
||||
new Proposal("bird", "bird=*", true, "bird=", 5), //
|
||||
new Proposal("dog", "dog=*", true, "dog=", 4), //
|
||||
new Proposal("method", "method=*", true, "method=", 7), //
|
||||
new Proposal("source", "source=*", true, "source=", 7)//
|
||||
);
|
||||
}
|
||||
|
||||
public void testPrefixOfKey() throws Exception {
|
||||
assertProposals("bi|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("bird", "bird=* ", true, "bird=", 5) //
|
||||
);
|
||||
assertProposals("bird|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("bird", "bird=* ", true, "bird=", 5) //
|
||||
);
|
||||
assertProposals("bird=eagle and n|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("name", "bird=eagle and name=* ", true, "bird=eagle and name=", 20) //
|
||||
);
|
||||
|
||||
assertProposals("|bird", ResultMode.FULL_VALUES, //
|
||||
new Proposal("bird", "bird=* ", true, "bird=", 5), //
|
||||
new Proposal("dog", "dog=* ", true, "dog=", 4), //
|
||||
new Proposal("method", "method=* ", true, "method=", 7), //
|
||||
new Proposal("name", "name=* ", true, "name=", 5), //
|
||||
new Proposal("source", "source=* ", true, "source=", 7) //
|
||||
);
|
||||
}
|
||||
public void testPrefixOfKey() throws Exception {
|
||||
assertProposals("bi|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("bird", "bird=* ", true, "bird=", 5) //
|
||||
);
|
||||
assertProposals("bird|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("bird", "bird=* ", true, "bird=", 5) //
|
||||
);
|
||||
assertProposals("bird=eagle and n|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("name", "bird=eagle and name=* ", true, "bird=eagle and name=", 20) //
|
||||
);
|
||||
|
||||
public void testPrefixOfValue() throws Exception {
|
||||
assertProposals("name =Tim|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Tim", "name =Tim", true, "name =Tim", 9),
|
||||
new Proposal("Timothy", "name =Timothy", true, "name =Timothy", 13));
|
||||
assertProposals("|bird", ResultMode.FULL_VALUES, //
|
||||
new Proposal("bird", "bird=* ", true, "bird=", 5), //
|
||||
new Proposal("dog", "dog=* ", true, "dog=", 4), //
|
||||
new Proposal("method", "method=* ", true, "method=", 7), //
|
||||
new Proposal("name", "name=* ", true, "name=", 5), //
|
||||
new Proposal("source", "source=* ", true, "source=", 7) //
|
||||
);
|
||||
}
|
||||
|
||||
assertProposals("name =Je|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name =Jennifer", true, "name =Jennifer", 14), //
|
||||
new Proposal("Jenny", "name =Jenny", true, "name =Jenny", 11) //
|
||||
);
|
||||
assertProposals("name =Tim,Je|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name =Tim,Jennifer", true, "name =Tim,Jennifer", 18), //
|
||||
new Proposal("Jenny", "name =Tim,Jenny", true, "name =Tim,Jenny", 15) //
|
||||
);
|
||||
|
||||
// TODO this case is currently handled completely wrong - it is handled similar to an empty query
|
||||
public void testPrefixOfValue() throws Exception {
|
||||
assertProposals("name =Tim|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Tim", "name =Tim", true, "name =Tim", 9),
|
||||
new Proposal("Timothy", "name =Timothy", true, "name =Timothy", 13));
|
||||
|
||||
assertProposals("name =Je|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name =Jennifer", true, "name =Jennifer", 14), //
|
||||
new Proposal("Jenny", "name =Jenny", true, "name =Jenny", 11) //
|
||||
);
|
||||
assertProposals("name =Tim,Je|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name =Tim,Jennifer", true, "name =Tim,Jennifer", 18), //
|
||||
new Proposal("Jenny", "name =Tim,Jenny", true, "name =Tim,Jenny", 15) //
|
||||
);
|
||||
|
||||
// TODO this case is currently handled completely wrong - it is handled similar
|
||||
// to an empty query
|
||||
// assertProposals("|bird=eagle and name=Tim", ResultMode.FULL_VALUES, //
|
||||
// new Proposal("Jennifer", "name =Tim,Jennifer", true, "name =Tim,Jennifer", 18), //
|
||||
// new Proposal("Jenny", "name =Tim,Jenny", true, "name =Tim,Jenny", 15) //
|
||||
// );
|
||||
|
||||
/*
|
||||
*/
|
||||
}
|
||||
|
||||
@Test(enabled = true)
|
||||
public void testInExpressions() throws Exception {
|
||||
assertProposals("name = (Timothy,|)", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name = (Timothy,Jennifer)", true, "name = (Timothy,Jennifer)", 24), //
|
||||
new Proposal("Jenny", "name = (Timothy,Jenny)", true, "name = (Timothy,Jenny)", 21), //
|
||||
new Proposal("Tim", "name = (Timothy,Tim)", true, "name = (Timothy,Tim)", 19), //
|
||||
new Proposal("Timothy", "name = (Timothy,Timothy)", true, "name = (Timothy,Timothy)", 23)//
|
||||
);
|
||||
/*
|
||||
*/
|
||||
}
|
||||
|
||||
assertProposals("name = (Timothy, J|)", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name = (Timothy, Jennifer)", true, "name = (Timothy, Jennifer)", 25), //
|
||||
new Proposal("Jenny", "name = (Timothy, Jenny)", true, "name = (Timothy, Jenny)", 22));
|
||||
@Test(enabled = true)
|
||||
public void testInExpressions() throws Exception {
|
||||
assertProposals("name = (Timothy,|)", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name = (Timothy,Jennifer)", true, "name = (Timothy,Jennifer)", 24), //
|
||||
new Proposal("Jenny", "name = (Timothy,Jenny)", true, "name = (Timothy,Jenny)", 21), //
|
||||
new Proposal("Tim", "name = (Timothy,Tim)", true, "name = (Timothy,Tim)", 19), //
|
||||
new Proposal("Timothy", "name = (Timothy,Timothy)", true, "name = (Timothy,Timothy)", 23)//
|
||||
);
|
||||
|
||||
assertProposals("name = (Tim|)", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Tim", "name = (Tim)", true, "name = (Tim)", 11),
|
||||
new Proposal("Timothy", "name = (Timothy)", true, "name = (Timothy)", 15));
|
||||
assertProposals("name = (Timothy, J|)", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name = (Timothy, Jennifer)", true, "name = (Timothy, Jennifer)", 25), //
|
||||
new Proposal("Jenny", "name = (Timothy, Jenny)", true, "name = (Timothy, Jenny)", 22));
|
||||
|
||||
/*
|
||||
*/
|
||||
}
|
||||
assertProposals("name = (Tim|)", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Tim", "name = (Tim)", true, "name = (Tim)", 11),
|
||||
new Proposal("Timothy", "name = (Timothy)", true, "name = (Timothy)", 15));
|
||||
|
||||
public void testProposalOnEmptyValuePrefix() throws Exception {
|
||||
assertProposals("name=|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name=Jennifer", true, "name=Jennifer", 13), //
|
||||
new Proposal("Jenny", "name=Jenny", true, "name=Jenny", 10), //
|
||||
new Proposal("Tim", "name=Tim", true, "name=Tim", 8), //
|
||||
new Proposal("Timothy", "name=Timothy", true, "name=Timothy", 12) //
|
||||
);
|
||||
/*
|
||||
*/
|
||||
}
|
||||
|
||||
assertProposals("method=|", ResultMode.CUT_AT_DOT, //
|
||||
new Proposal("FooController.", "method=FooController.", true, "method=FooController.", 21), //
|
||||
new Proposal("FooService.", "method=FooService.", true, "method=FooService.", 18), //
|
||||
new Proposal("BarController.", "method=BarController.", true, "method=BarController.", 21), //
|
||||
new Proposal("FooBarService.", "method=FooBarService.", true, "method=FooBarService.", 21) //
|
||||
);
|
||||
assertProposals("method=|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("FooController.doImportantStuff", "method=FooController.doImportantStuff", true,
|
||||
"method=FooController.doImportantStuff", 37), //
|
||||
new Proposal("FooService.doImportantStuff", "method=FooService.doImportantStuff", true,
|
||||
"method=FooService.doImportantStuff", 34), //
|
||||
new Proposal("FooBarService.doOtherStuff", "method=FooBarService.doOtherStuff", true,
|
||||
"method=FooBarService.doOtherStuff", 33), //
|
||||
new Proposal("BarController.doBoringStuff", "method=BarController.doBoringStuff", true,
|
||||
"method=BarController.doBoringStuff", 34) //
|
||||
);
|
||||
}
|
||||
public void testProposalOnEmptyValuePrefix() throws Exception {
|
||||
assertProposals("name=|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("Jennifer", "name=Jennifer", true, "name=Jennifer", 13), //
|
||||
new Proposal("Jenny", "name=Jenny", true, "name=Jenny", 10), //
|
||||
new Proposal("Tim", "name=Tim", true, "name=Tim", 8), //
|
||||
new Proposal("Timothy", "name=Timothy", true, "name=Timothy", 12) //
|
||||
);
|
||||
|
||||
public void testProposalOnValueSmartExpression() throws Exception {
|
||||
assertProposals("method=Foo.|", ResultMode.CUT_AT_DOT, //
|
||||
new Proposal("FooController.doImportantStuff", "method=FooController.doImportantStuff", true,
|
||||
"method=FooController.doImportantStuff", 37), //
|
||||
new Proposal("FooService.doImportantStuff", "method=FooService.doImportantStuff", true,
|
||||
"method=FooService.doImportantStuff", 34), //
|
||||
new Proposal("FooBarService.doOtherStuff", "method=FooBarService.doOtherStuff", true,
|
||||
"method=FooBarService.doOtherStuff", 33) //
|
||||
);
|
||||
assertProposals("method=|", ResultMode.CUT_AT_DOT, //
|
||||
new Proposal("FooController.", "method=FooController.", true, "method=FooController.", 21), //
|
||||
new Proposal("FooService.", "method=FooService.", true, "method=FooService.", 18), //
|
||||
new Proposal("BarController.", "method=BarController.", true, "method=BarController.", 21), //
|
||||
new Proposal("FooBarService.", "method=FooBarService.", true, "method=FooBarService.", 21) //
|
||||
);
|
||||
assertProposals("method=|", ResultMode.FULL_VALUES, //
|
||||
new Proposal("FooController.doImportantStuff", "method=FooController.doImportantStuff", true,
|
||||
"method=FooController.doImportantStuff", 37), //
|
||||
new Proposal("FooService.doImportantStuff", "method=FooService.doImportantStuff", true,
|
||||
"method=FooService.doImportantStuff", 34), //
|
||||
new Proposal("FooBarService.doOtherStuff", "method=FooBarService.doOtherStuff", true,
|
||||
"method=FooBarService.doOtherStuff", 33), //
|
||||
new Proposal("BarController.doBoringStuff", "method=BarController.doBoringStuff", true,
|
||||
"method=BarController.doBoringStuff", 34) //
|
||||
);
|
||||
}
|
||||
|
||||
assertProposals("method=Foo.*Stuf|", ResultMode.CUT_AT_DOT, //
|
||||
new Proposal("FooController.doImportantStuff", "method=FooController.doImportantStuff", true,
|
||||
"method=FooController.doImportantStuff", 37), //
|
||||
new Proposal("FooService.doImportantStuff", "method=FooService.doImportantStuff", true,
|
||||
"method=FooService.doImportantStuff", 34), //
|
||||
new Proposal("FooBarService.doOtherStuff", "method=FooBarService.doOtherStuff", true,
|
||||
"method=FooBarService.doOtherStuff", 33) //
|
||||
);
|
||||
public void testProposalOnValueSmartExpression() throws Exception {
|
||||
assertProposals("method=Foo.|", ResultMode.CUT_AT_DOT, //
|
||||
new Proposal("FooController.doImportantStuff", "method=FooController.doImportantStuff", true,
|
||||
"method=FooController.doImportantStuff", 37), //
|
||||
new Proposal("FooService.doImportantStuff", "method=FooService.doImportantStuff", true,
|
||||
"method=FooService.doImportantStuff", 34), //
|
||||
new Proposal("FooBarService.doOtherStuff", "method=FooBarService.doOtherStuff", true,
|
||||
"method=FooBarService.doOtherStuff", 33) //
|
||||
);
|
||||
|
||||
// returns nothing, because GloblikePattern.globlikeToRegex() returns the
|
||||
// following regex: ^[a-z]*Foo.*\.[a-z]*Stuf
|
||||
// Maybe I will change that some day and allow upper case characters before
|
||||
// "Stuff".
|
||||
assertProposals("method=Foo.Stuf|", ResultMode.CUT_AT_DOT);
|
||||
assertProposals("method=Foo.*Stuf|", ResultMode.CUT_AT_DOT, //
|
||||
new Proposal("FooController.doImportantStuff", "method=FooController.doImportantStuff", true,
|
||||
"method=FooController.doImportantStuff", 37), //
|
||||
new Proposal("FooService.doImportantStuff", "method=FooService.doImportantStuff", true,
|
||||
"method=FooService.doImportantStuff", 34), //
|
||||
new Proposal("FooBarService.doOtherStuff", "method=FooBarService.doOtherStuff", true,
|
||||
"method=FooBarService.doOtherStuff", 33) //
|
||||
);
|
||||
|
||||
assertProposals("method=Foo.Im", ResultMode.CUT_AT_DOT, 13, //
|
||||
new Proposal("FooController.doImportantStuff", "method=FooController.doImportantStuff", true,
|
||||
"method=FooController.doImportantStuff", 37), //
|
||||
new Proposal("FooService.doImportantStuff", "method=FooService.doImportantStuff", true,
|
||||
"method=FooService.doImportantStuff", 34) //
|
||||
);
|
||||
}
|
||||
// returns nothing, because GloblikePattern.globlikeToRegex() returns the
|
||||
// following regex: ^[a-z]*Foo.*\.[a-z]*Stuf
|
||||
// Maybe I will change that some day and allow upper case characters before
|
||||
// "Stuff".
|
||||
assertProposals("method=Foo.Stuf|", ResultMode.CUT_AT_DOT);
|
||||
|
||||
public void testProposalOnEmptyKeyPrefix() throws Exception {
|
||||
assertProposals("name=* and |", ResultMode.FULL_VALUES, //
|
||||
proposal("name", "name=* and name=* ", "name=* and name=|"), //
|
||||
proposal("bird", "name=* and bird=* ", "name=* and bird=|"), //
|
||||
proposal("dog", "name=* and dog=* ", "name=* and dog=|"), //
|
||||
// TODO it is wrong to return those two, because there are no values with name
|
||||
// and type|address, but I'll leave this for now, because this is a different
|
||||
// issue
|
||||
proposal("method", "name=* and method=* ", "name=* and method=|"), //
|
||||
proposal("source", "name=* and source=* ", "name=* and source=|")//
|
||||
);
|
||||
}
|
||||
assertProposals("method=Foo.Im", ResultMode.CUT_AT_DOT, 13, //
|
||||
new Proposal("FooController.doImportantStuff", "method=FooController.doImportantStuff", true,
|
||||
"method=FooController.doImportantStuff", 37), //
|
||||
new Proposal("FooService.doImportantStuff", "method=FooService.doImportantStuff", true,
|
||||
"method=FooService.doImportantStuff", 34) //
|
||||
);
|
||||
}
|
||||
|
||||
public void testProposalWithWildcards() throws Exception {
|
||||
public void testProposalOnEmptyKeyPrefix() throws Exception {
|
||||
assertProposals("name=* and |", ResultMode.FULL_VALUES, //
|
||||
proposal("name", "name=* and name=* ", "name=* and name=|"), //
|
||||
proposal("bird", "name=* and bird=* ", "name=* and bird=|"), //
|
||||
proposal("dog", "name=* and dog=* ", "name=* and dog=|"), //
|
||||
// TODO it is wrong to return those two, because there are no values with name
|
||||
// and type|address, but I'll leave this for now, because this is a different
|
||||
// issue
|
||||
proposal("method", "name=* and method=* ", "name=* and method=|"), //
|
||||
proposal("source", "name=* and source=* ", "name=* and source=|")//
|
||||
);
|
||||
}
|
||||
|
||||
assertProposals("name=*im|", ResultMode.FULL_VALUES, //
|
||||
proposal("Tim", "name=Tim", "name=Tim|"), //
|
||||
proposal("Timothy", "name=Timothy", "name=Timothy|")//
|
||||
);
|
||||
public void testProposalWithWildcards() throws Exception {
|
||||
|
||||
assertProposals("(method=FooService.doIS,FooController.*) and method=|", ResultMode.FULL_VALUES, //
|
||||
proposal("FooService.doImportantStuff",
|
||||
"(method=FooService.doIS,FooController.*) and method=FooService.doImportantStuff",
|
||||
"(method=FooService.doIS,FooController.*) and method=FooService.doImportantStuff|"), //
|
||||
proposal("FooController.doImportantStuff",
|
||||
"(method=FooService.doIS,FooController.*) and method=FooController.doImportantStuff",
|
||||
"(method=FooService.doIS,FooController.*) and method=FooController.doImportantStuff|")//
|
||||
);
|
||||
}
|
||||
assertProposals("name=*im|", ResultMode.FULL_VALUES, //
|
||||
proposal("Tim", "name=Tim", "name=Tim|"), //
|
||||
proposal("Timothy", "name=Timothy", "name=Timothy|")//
|
||||
);
|
||||
|
||||
public void testProposalWithAndExpression() throws Exception {
|
||||
assertProposals("name=*im| and bird=eagle", ResultMode.FULL_VALUES, //
|
||||
proposal("Tim", "name=Tim and bird=eagle", "name=Tim| and bird=eagle"), //
|
||||
proposal("Timothy", "name=Timothy and bird=eagle", "name=Timothy| and bird=eagle")//
|
||||
);
|
||||
assertProposals("(method=FooService.doIS,FooController.*) and method=|", ResultMode.FULL_VALUES, //
|
||||
proposal("FooService.doImportantStuff",
|
||||
"(method=FooService.doIS,FooController.*) and method=FooService.doImportantStuff",
|
||||
"(method=FooService.doIS,FooController.*) and method=FooService.doImportantStuff|"), //
|
||||
proposal("FooController.doImportantStuff",
|
||||
"(method=FooService.doIS,FooController.*) and method=FooController.doImportantStuff",
|
||||
"(method=FooService.doIS,FooController.*) and method=FooController.doImportantStuff|")//
|
||||
);
|
||||
}
|
||||
|
||||
assertProposals("name=*im| and bird=eagle,pigeon", ResultMode.FULL_VALUES, //
|
||||
proposal("Tim", "name=Tim and bird=eagle,pigeon", "name=Tim| and bird=eagle,pigeon"), //
|
||||
proposal("Timothy", "name=Timothy and bird=eagle,pigeon", "name=Timothy| and bird=eagle,pigeon")//
|
||||
);
|
||||
}
|
||||
public void testProposalWithAndExpression() throws Exception {
|
||||
assertProposals("name=*im| and bird=eagle", ResultMode.FULL_VALUES, //
|
||||
proposal("Tim", "name=Tim and bird=eagle", "name=Tim| and bird=eagle"), //
|
||||
proposal("Timothy", "name=Timothy and bird=eagle", "name=Timothy| and bird=eagle")//
|
||||
);
|
||||
|
||||
public void testProposalWithAndNotExpression() throws Exception {
|
||||
assertProposals("name=Tim and ! dog=labrador and bird=|", ResultMode.FULL_VALUES, //
|
||||
proposal("eagle", "name=Tim and ! dog=labrador and bird=eagle",
|
||||
"name=Tim and ! dog=labrador and bird=eagle|") //
|
||||
);
|
||||
assertProposals("name=Tim and not dog=labrador and bird=|", ResultMode.FULL_VALUES, //
|
||||
proposal("eagle", "name=Tim and not dog=labrador and bird=eagle",
|
||||
"name=Tim and not dog=labrador and bird=eagle|") //
|
||||
);
|
||||
}
|
||||
assertProposals("name=*im| and bird=eagle,pigeon", ResultMode.FULL_VALUES, //
|
||||
proposal("Tim", "name=Tim and bird=eagle,pigeon", "name=Tim| and bird=eagle,pigeon"), //
|
||||
proposal("Timothy", "name=Timothy and bird=eagle,pigeon", "name=Timothy| and bird=eagle,pigeon")//
|
||||
);
|
||||
}
|
||||
|
||||
private Proposal proposal(final String proposedTag, final String proposedQuery, final String newQuery) {
|
||||
final String newQueryWithoutCaretMarker = newQuery.replace("|", "");
|
||||
final int newCaretPosition = newQuery.indexOf('|');
|
||||
return new Proposal(proposedTag, proposedQuery, true, newQueryWithoutCaretMarker, newCaretPosition);
|
||||
}
|
||||
public void testProposalWithAndNotExpression() throws Exception {
|
||||
assertProposals("name=Tim and ! dog=labrador and bird=|", ResultMode.FULL_VALUES, //
|
||||
proposal("eagle", "name=Tim and ! dog=labrador and bird=eagle",
|
||||
"name=Tim and ! dog=labrador and bird=eagle|") //
|
||||
);
|
||||
assertProposals("name=Tim and not dog=labrador and bird=|", ResultMode.FULL_VALUES, //
|
||||
proposal("eagle", "name=Tim and not dog=labrador and bird=eagle",
|
||||
"name=Tim and not dog=labrador and bird=eagle|") //
|
||||
);
|
||||
}
|
||||
|
||||
private void assertProposals(final String query, final ResultMode resultMode, final Proposal... expected)
|
||||
throws InterruptedException {
|
||||
final int caretIndex = query.indexOf("|");
|
||||
final String q = query.replace("|", "");
|
||||
assertProposals(q, resultMode, caretIndex, expected);
|
||||
}
|
||||
private Proposal proposal(final String proposedTag, final String proposedQuery, final String newQuery) {
|
||||
final String newQueryWithoutCaretMarker = newQuery.replace("|", "");
|
||||
final int newCaretPosition = newQuery.indexOf('|');
|
||||
return new Proposal(proposedTag, proposedQuery, true, newQueryWithoutCaretMarker, newCaretPosition);
|
||||
}
|
||||
|
||||
private void assertProposals(final String query, final ResultMode resultMode, final int caretIndex,
|
||||
final Proposal... expected) throws InterruptedException {
|
||||
private void assertProposals(final String query, final ResultMode resultMode, final Proposal... expected)
|
||||
throws InterruptedException {
|
||||
final int caretIndex = query.indexOf("|");
|
||||
final String q = query.replace("|", "");
|
||||
assertProposals(q, resultMode, caretIndex, expected);
|
||||
}
|
||||
|
||||
final List<Proposal> actual = dataStore
|
||||
.propose(new QueryWithCaretMarker(query, dateRange, caretIndex, resultMode));
|
||||
final List<Proposal> expectedList = Arrays.asList(expected);
|
||||
Collections.sort(expectedList);
|
||||
private void assertProposals(final String query, final ResultMode resultMode, final int caretIndex,
|
||||
final Proposal... expected) throws InterruptedException {
|
||||
|
||||
System.out.println("\n\n--- " + query + " ---");
|
||||
System.out.println("actual : " + String.join("\n", CollectionUtils.map(actual, Proposal::toString)));
|
||||
System.out.println("expected: " + String.join("\n", CollectionUtils.map(expectedList, Proposal::toString)));
|
||||
Assert.assertEquals(actual, expectedList);
|
||||
}
|
||||
final List<Proposal> actual = dataStore
|
||||
.propose(new QueryWithCaretMarker(query, dateRange, caretIndex, resultMode));
|
||||
final List<Proposal> expectedList = Arrays.asList(expected);
|
||||
Collections.sort(expectedList);
|
||||
|
||||
System.out.println("\n\n--- " + query + " ---");
|
||||
System.out.println("actual : " + String.join("\n", CollectionUtils.map(actual, Proposal::toString)));
|
||||
System.out.println("expected: " + String.join("\n", CollectionUtils.map(expectedList, Proposal::toString)));
|
||||
Assert.assertEquals(actual, expectedList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,53 +21,53 @@ import org.testng.annotations.Test;
|
||||
@Test
|
||||
public class QueryCompletionIndexTest {
|
||||
|
||||
private Path dataDirectory;
|
||||
private Path dataDirectory;
|
||||
|
||||
@BeforeMethod
|
||||
public void beforeMethod() throws IOException {
|
||||
dataDirectory = Files.createTempDirectory("pdb");
|
||||
}
|
||||
@BeforeMethod
|
||||
public void beforeMethod() throws IOException {
|
||||
dataDirectory = Files.createTempDirectory("pdb");
|
||||
}
|
||||
|
||||
@AfterMethod
|
||||
public void afterMethod() throws IOException {
|
||||
FileUtils.delete(dataDirectory);
|
||||
}
|
||||
@AfterMethod
|
||||
public void afterMethod() throws IOException {
|
||||
FileUtils.delete(dataDirectory);
|
||||
}
|
||||
|
||||
public void test() throws Exception {
|
||||
Tags.STRING_COMPRESSOR = new StringCompressor(new UniqueStringIntegerPairs());
|
||||
public void test() throws Exception {
|
||||
Tags.STRING_COMPRESSOR = new StringCompressor(new UniqueStringIntegerPairs());
|
||||
|
||||
final List<Tags> tags = Arrays.asList(//
|
||||
Tags.createAndAddToDictionary("firstname", "John", "lastname", "Doe", "country", "Atlantis"), // A
|
||||
Tags.createAndAddToDictionary("firstname", "Jane", "lastname", "Doe", "country", "ElDorado"), // B
|
||||
Tags.createAndAddToDictionary("firstname", "John", "lastname", "Miller", "country", "Atlantis")// C
|
||||
);
|
||||
final List<Tags> tags = Arrays.asList(//
|
||||
Tags.createAndAddToDictionary("firstname", "John", "lastname", "Doe", "country", "Atlantis"), // A
|
||||
Tags.createAndAddToDictionary("firstname", "Jane", "lastname", "Doe", "country", "ElDorado"), // B
|
||||
Tags.createAndAddToDictionary("firstname", "John", "lastname", "Miller", "country", "Atlantis")// C
|
||||
);
|
||||
|
||||
final DateTimeRange dateRange = DateTimeRange.relativeMillis(1);
|
||||
final ParititionId partitionId = DateIndexExtension.toPartitionIds(dateRange).get(0);
|
||||
final DateTimeRange dateRange = DateTimeRange.relativeMillis(1);
|
||||
final ParititionId partitionId = DateIndexExtension.toPartitionIds(dateRange).get(0);
|
||||
|
||||
try (QueryCompletionIndex index = new QueryCompletionIndex(dataDirectory)) {
|
||||
for (final Tags t : tags) {
|
||||
index.addTags(partitionId, t);
|
||||
}
|
||||
try (QueryCompletionIndex index = new QueryCompletionIndex(dataDirectory)) {
|
||||
for (final Tags t : tags) {
|
||||
index.addTags(partitionId, t);
|
||||
}
|
||||
|
||||
// all firstnames where lastname=Doe are returned sorted alphabetically.
|
||||
// tags A and B match
|
||||
final SortedSet<String> firstnamesWithLastnameDoe = index.find(dateRange, new Tag("lastname", "Doe"),
|
||||
"firstname");
|
||||
Assert.assertEquals(firstnamesWithLastnameDoe, Arrays.asList("Jane", "John"));
|
||||
// all firstnames where lastname=Doe are returned sorted alphabetically.
|
||||
// tags A and B match
|
||||
final SortedSet<String> firstnamesWithLastnameDoe = index.find(dateRange, new Tag("lastname", "Doe"),
|
||||
"firstname");
|
||||
Assert.assertEquals(firstnamesWithLastnameDoe, Arrays.asList("Jane", "John"));
|
||||
|
||||
// no duplicates are returned:
|
||||
// tags A and C match firstname=John, but both have country=Atlantis
|
||||
final SortedSet<String> countryWithFirstnameJohn = index.find(dateRange, new Tag("firstname", "John"),
|
||||
"country");
|
||||
Assert.assertEquals(countryWithFirstnameJohn, Arrays.asList("Atlantis"));
|
||||
// no duplicates are returned:
|
||||
// tags A and C match firstname=John, but both have country=Atlantis
|
||||
final SortedSet<String> countryWithFirstnameJohn = index.find(dateRange, new Tag("firstname", "John"),
|
||||
"country");
|
||||
Assert.assertEquals(countryWithFirstnameJohn, Arrays.asList("Atlantis"));
|
||||
|
||||
// findAllValuesForField sorts alphabetically
|
||||
final SortedSet<String> firstnames = index.findAllValuesForField(dateRange, "firstname");
|
||||
Assert.assertEquals(firstnames, Arrays.asList("Jane", "John"), "found: " + firstnames);
|
||||
// findAllValuesForField sorts alphabetically
|
||||
final SortedSet<String> firstnames = index.findAllValuesForField(dateRange, "firstname");
|
||||
Assert.assertEquals(firstnames, Arrays.asList("Jane", "John"), "found: " + firstnames);
|
||||
|
||||
final SortedSet<String> countries = index.findAllValuesForField(dateRange, "country");
|
||||
Assert.assertEquals(countries, Arrays.asList("Atlantis", "ElDorado"));
|
||||
}
|
||||
}
|
||||
final SortedSet<String> countries = index.findAllValuesForField(dateRange, "country");
|
||||
Assert.assertEquals(countries, Arrays.asList("Atlantis", "ElDorado"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,54 +12,54 @@ import org.testng.annotations.Test;
|
||||
@Test
|
||||
public class CandidateGrouperTest {
|
||||
|
||||
@DataProvider
|
||||
public Object[][] providerGroup() {
|
||||
final List<Object[]> result = new ArrayList<>();
|
||||
@DataProvider
|
||||
public Object[][] providerGroup() {
|
||||
final List<Object[]> result = new ArrayList<>();
|
||||
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.yy.BB", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = |", //
|
||||
Set.of("aa.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.yy.BB", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = a|", //
|
||||
Set.of("aa.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.yy.BB", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = aa|", //
|
||||
Set.of("aa.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.yy.BB", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = aa.|", //
|
||||
Set.of("aa.xx.", "aa.yy.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = aa.x|", //
|
||||
Set.of("aa.xx.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = aa.xx.|", //
|
||||
Set.of("aa.xx.AA.", "aa.xx.BB") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.xx.AA.YY"), //
|
||||
"name = aa.xx.AA.|", //
|
||||
Set.of("aa.xx.AA.XX", "aa.xx.AA.YY") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("XX.YY.ZZ", "XX.YY"), //
|
||||
"name = XX.Y|", //
|
||||
Set.of("XX.YY.", "XX.YY") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.yy.BB", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = |", //
|
||||
Set.of("aa.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.yy.BB", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = a|", //
|
||||
Set.of("aa.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.yy.BB", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = aa|", //
|
||||
Set.of("aa.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.yy.BB", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = aa.|", //
|
||||
Set.of("aa.xx.", "aa.yy.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = aa.x|", //
|
||||
Set.of("aa.xx.") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.xx.BB", "aa.xx.AA.YY"), //
|
||||
"name = aa.xx.|", //
|
||||
Set.of("aa.xx.AA.", "aa.xx.BB") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("aa.xx.AA.XX", "aa.xx.AA.YY"), //
|
||||
"name = aa.xx.AA.|", //
|
||||
Set.of("aa.xx.AA.XX", "aa.xx.AA.YY") });
|
||||
result.add(new Object[] { //
|
||||
Set.of("XX.YY.ZZ", "XX.YY"), //
|
||||
"name = XX.Y|", //
|
||||
Set.of("XX.YY.", "XX.YY") });
|
||||
|
||||
return result.toArray(new Object[0][]);
|
||||
}
|
||||
return result.toArray(new Object[0][]);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "providerGroup")
|
||||
public void testGroup(final Set<String> values, final String queryWithCaretMarker, final Set<String> expected) {
|
||||
final CandidateGrouper grouper = new CandidateGrouper();
|
||||
@Test(dataProvider = "providerGroup")
|
||||
public void testGroup(final Set<String> values, final String queryWithCaretMarker, final Set<String> expected) {
|
||||
final CandidateGrouper grouper = new CandidateGrouper();
|
||||
|
||||
final String query = queryWithCaretMarker.replace("|", NewProposerParser.CARET_MARKER);
|
||||
final String query = queryWithCaretMarker.replace("|", NewProposerParser.CARET_MARKER);
|
||||
|
||||
final SortedSet<String> actual = grouper.group(values, query);
|
||||
final SortedSet<String> actual = grouper.group(values, query);
|
||||
|
||||
Assert.assertEquals(actual, expected);
|
||||
}
|
||||
Assert.assertEquals(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user