Refactor lucene results
This commit is contained in:
parent
7f15a6e099
commit
319abeaf30
@ -7,6 +7,7 @@ import it.cavallium.dbengine.client.query.current.data.ScoreSort;
|
||||
import it.cavallium.dbengine.database.LLDocument;
|
||||
import it.cavallium.dbengine.database.LLItem;
|
||||
import it.cavallium.dbengine.database.LLLuceneIndex;
|
||||
import it.cavallium.dbengine.database.LLSignal;
|
||||
import it.cavallium.dbengine.database.LLTerm;
|
||||
import it.cavallium.dbengine.database.disk.LLLocalDatabaseConnection;
|
||||
import it.cavallium.dbengine.lucene.LuceneUtils;
|
||||
@ -46,11 +47,15 @@ public class IndicizationExample {
|
||||
.build(),
|
||||
"id"
|
||||
))
|
||||
.flatMap(results -> results
|
||||
.flatMap(results -> Mono.from(results
|
||||
.results()
|
||||
.flatMap(r -> r)
|
||||
.doOnNext(value -> System.out.println("Value: " + value))
|
||||
.then(results.totalHitsCount())
|
||||
.doOnNext(signal -> {
|
||||
if (signal.isValue()) {
|
||||
System.out.println("Value: " + signal.getValue());
|
||||
}
|
||||
})
|
||||
.filter(LLSignal::isTotalHitsCount))
|
||||
)
|
||||
.doOnNext(count -> System.out.println("Total hits: " + count))
|
||||
.doOnTerminate(() -> System.out.println("Completed"))
|
||||
@ -122,7 +127,11 @@ public class IndicizationExample {
|
||||
.flatMap(results -> LuceneUtils.mergeStream(results
|
||||
.results(), MultiSort.topScoreRaw(), 10L)
|
||||
.doOnNext(value -> System.out.println("Value: " + value))
|
||||
.then(results.totalHitsCount())
|
||||
.then(Mono.from(results
|
||||
.results()
|
||||
.flatMap(part -> part)
|
||||
.filter(LLSignal::isTotalHitsCount)
|
||||
.map(LLSignal::getTotalHitsCount)))
|
||||
)
|
||||
.doOnNext(count -> System.out.println("Total hits: " + count))
|
||||
.doOnTerminate(() -> System.out.println("Completed"))
|
||||
|
@ -82,14 +82,39 @@ public class LuceneIndex<T, U> implements LLSnapshottable {
|
||||
@Nullable MultiSort<SearchResultKey<T>> sort,
|
||||
LLScoreMode scoreMode,
|
||||
@Nullable Long limit) {
|
||||
var mappedKeys = llSearchResult
|
||||
Flux<Flux<LuceneSignal<SearchResultKey<T>>>> mappedKeys = llSearchResult
|
||||
.results()
|
||||
.map(flux -> flux.map(item -> new SearchResultKey<>(indicizer.getKey(item.getKey()), item.getScore())));
|
||||
.map(flux -> flux.map(signal -> {
|
||||
if (signal.isValue()) {
|
||||
return LuceneSignal.value(
|
||||
new SearchResultKey<T>(indicizer.getKey(signal.getValue().getKey()),
|
||||
signal.getValue().getScore()
|
||||
));
|
||||
} else {
|
||||
return LuceneSignal.totalHitsCount(signal.getTotalHitsCount());
|
||||
}
|
||||
}));
|
||||
MultiSort<SearchResultKey<T>> finalSort;
|
||||
if (scoreMode != LLScoreMode.COMPLETE_NO_SCORES && sort == null) {
|
||||
sort = MultiSort.topScore();
|
||||
finalSort = MultiSort.topScore();
|
||||
} else {
|
||||
finalSort = sort;
|
||||
}
|
||||
var sortedKeys = LuceneUtils.mergeStream(mappedKeys, sort, limit);
|
||||
return new SearchResultKeys<>(llSearchResult.totalHitsCount(), sortedKeys);
|
||||
|
||||
MultiSort<LuceneSignal<SearchResultKey<T>>> mappedSort;
|
||||
if (finalSort != null) {
|
||||
mappedSort = new MultiSort<>(finalSort.getQuerySort(), (signal1, signal2) -> {
|
||||
if (signal1.isValue() && signal2.isValue()) {
|
||||
return finalSort.getResultSort().compare((signal1.getValue()), signal2.getValue());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mappedSort = null;
|
||||
}
|
||||
Flux<LuceneSignal<SearchResultKey<T>>> sortedKeys = LuceneUtils.mergeStream(mappedKeys, mappedSort, limit);
|
||||
return new SearchResultKeys<>(sortedKeys);
|
||||
}
|
||||
|
||||
private SearchResult<T, U> transformLuceneResultWithValues(LLSearchResult llSearchResult,
|
||||
@ -97,17 +122,39 @@ public class LuceneIndex<T, U> implements LLSnapshottable {
|
||||
LLScoreMode scoreMode,
|
||||
@Nullable Long limit,
|
||||
ValueGetter<T, U> valueGetter) {
|
||||
var mappedKeys = llSearchResult
|
||||
Flux<Flux<LuceneSignal<SearchResultItem<T, U>>>> mappedKeys = llSearchResult
|
||||
.results()
|
||||
.map(flux -> flux.flatMapSequential(item -> {
|
||||
var key = indicizer.getKey(item.getKey());
|
||||
return valueGetter.get(key).map(value -> new SearchResultItem<>(key, value, item.getScore()));
|
||||
.map(flux -> flux.flatMapSequential(signal -> {
|
||||
if (signal.isValue()) {
|
||||
var key = indicizer.getKey(signal.getValue().getKey());
|
||||
return valueGetter
|
||||
.get(key)
|
||||
.map(value -> LuceneSignal.value(new SearchResultItem<>(key, value, signal.getValue().getScore())));
|
||||
} else {
|
||||
return Mono.just(LuceneSignal.totalHitsCount((signal.getTotalHitsCount())));
|
||||
}
|
||||
}));
|
||||
MultiSort<SearchResultItem<T, U>> finalSort;
|
||||
if (scoreMode != LLScoreMode.COMPLETE_NO_SCORES && sort == null) {
|
||||
sort = MultiSort.topScoreWithValues();
|
||||
finalSort = MultiSort.topScoreWithValues();
|
||||
} else {
|
||||
finalSort = sort;
|
||||
}
|
||||
var sortedKeys = LuceneUtils.mergeStream(mappedKeys, sort, limit);
|
||||
return new SearchResult<>(llSearchResult.totalHitsCount(), sortedKeys);
|
||||
|
||||
MultiSort<LuceneSignal<SearchResultItem<T, U>>> mappedSort;
|
||||
if (finalSort != null) {
|
||||
mappedSort = new MultiSort<>(finalSort.getQuerySort(), (signal1, signal2) -> {
|
||||
if (signal1.isValue() && signal2.isValue()) {
|
||||
return finalSort.getResultSort().compare((signal1.getValue()), signal2.getValue());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mappedSort = null;
|
||||
}
|
||||
var sortedKeys = LuceneUtils.mergeStream(mappedKeys, mappedSort, limit);
|
||||
return new SearchResult<>(sortedKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,9 +247,10 @@ public class LuceneIndex<T, U> implements LLSnapshottable {
|
||||
}
|
||||
|
||||
public Mono<Long> count(@Nullable CompositeSnapshot snapshot, Query query) {
|
||||
return this.search(ClientQueryParams.<SearchResultKey<T>>builder().snapshot(snapshot).query(query).limit(0).build())
|
||||
.flatMap(SearchResultKeys::totalHitsCount)
|
||||
.single();
|
||||
return Mono.from(this.search(ClientQueryParams.<SearchResultKey<T>>builder().snapshot(snapshot).query(query).limit(0).build())
|
||||
.flatMapMany(SearchResultKeys::results)
|
||||
.filter(LuceneSignal::isTotalHitsCount)
|
||||
.map(LuceneSignal::getTotalHitsCount));
|
||||
}
|
||||
|
||||
public boolean isLowMemoryMode() {
|
||||
|
22
src/main/java/it/cavallium/dbengine/client/LuceneSignal.java
Normal file
22
src/main/java/it/cavallium/dbengine/client/LuceneSignal.java
Normal file
@ -0,0 +1,22 @@
|
||||
package it.cavallium.dbengine.client;
|
||||
|
||||
public interface LuceneSignal<T> {
|
||||
|
||||
boolean isValue();
|
||||
|
||||
boolean isTotalHitsCount();
|
||||
|
||||
T getValue();
|
||||
|
||||
long getTotalHitsCount();
|
||||
|
||||
static <T> LuceneSignalValue<T> value(T value) {
|
||||
return new LuceneSignalValue<>(value);
|
||||
}
|
||||
|
||||
static <T> LuceneSignalTotalHitsCount<T> totalHitsCount(long totalHitsCount) {
|
||||
return new LuceneSignalTotalHitsCount<>(totalHitsCount);
|
||||
}
|
||||
|
||||
<U> LuceneSignalTotalHitsCount<U> mapTotalHitsCount();
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package it.cavallium.dbengine.client;
|
||||
|
||||
public class LuceneSignalTotalHitsCount<T> implements LuceneSignal<T> {
|
||||
|
||||
private final long totalHitsCount;
|
||||
|
||||
public LuceneSignalTotalHitsCount(long totalHitsCount) {
|
||||
this.totalHitsCount = totalHitsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTotalHitsCount() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getValue() {
|
||||
throw new UnsupportedOperationException("This object is not value");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalHitsCount() {
|
||||
return totalHitsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> LuceneSignalTotalHitsCount<U> mapTotalHitsCount() {
|
||||
//noinspection unchecked
|
||||
return (LuceneSignalTotalHitsCount<U>) this;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package it.cavallium.dbengine.client;
|
||||
|
||||
public class LuceneSignalValue<T> implements LuceneSignal<T> {
|
||||
|
||||
private final T value;
|
||||
|
||||
public LuceneSignalValue(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean isValue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTotalHitsCount() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalHitsCount() {
|
||||
throw new UnsupportedOperationException("This object is not TotalHitsCount");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> LuceneSignalTotalHitsCount<U> mapTotalHitsCount() {
|
||||
throw new UnsupportedOperationException("This object is not TotalHitsCount");
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import it.cavallium.dbengine.client.query.current.data.RandomSort;
|
||||
import it.cavallium.dbengine.client.query.current.data.ScoreSort;
|
||||
import it.cavallium.dbengine.client.query.current.data.Sort;
|
||||
import it.cavallium.dbengine.database.LLKeyScore;
|
||||
import it.cavallium.dbengine.database.LLSignal;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.ToIntFunction;
|
||||
import java.util.function.ToLongFunction;
|
||||
@ -67,8 +68,15 @@ public class MultiSort<T> {
|
||||
return new MultiSort<>(RandomSort.of(), (a, b) -> 0);
|
||||
}
|
||||
|
||||
public static MultiSort<LLKeyScore> topScoreRaw() {
|
||||
return new MultiSort<>(ScoreSort.of(), Comparator.comparingDouble(LLKeyScore::getScore).reversed());
|
||||
public static MultiSort<LLSignal> topScoreRaw() {
|
||||
Comparator<LLKeyScore> comp = Comparator.comparingDouble(LLKeyScore::getScore).reversed();
|
||||
return new MultiSort<>(ScoreSort.of(), (signal1, signal2) -> {
|
||||
if (signal1.isValue() && signal2.isValue()) {
|
||||
return comp.compare(signal1.getValue(), signal2.getValue());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> MultiSort<SearchResultKey<T>> topScore() {
|
||||
|
@ -1,54 +1,35 @@
|
||||
package it.cavallium.dbengine.client;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class SearchResult<T, U> {
|
||||
|
||||
private final Mono<Long> totalHitsCount;
|
||||
private final Flux<SearchResultItem<T, U>> results;
|
||||
private final Flux<LuceneSignal<SearchResultItem<T, U>>> results;
|
||||
|
||||
public SearchResult(Mono<Long> totalHitsCount, Flux<SearchResultItem<T, U>> results) {
|
||||
this.totalHitsCount = totalHitsCount;
|
||||
public SearchResult(Flux<LuceneSignal<SearchResultItem<T, U>>> results) {
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public static <T, U> SearchResult<T, U> empty() {
|
||||
return new SearchResult<>(Mono.just(0L), Flux.empty());
|
||||
return new SearchResult<>(Flux.just(LuceneSignal.totalHitsCount(0L)));
|
||||
}
|
||||
|
||||
public Mono<Long> totalHitsCount() {
|
||||
return this.totalHitsCount;
|
||||
}
|
||||
|
||||
public Flux<SearchResultItem<T, U>> results() {
|
||||
public Flux<LuceneSignal<SearchResultItem<T, U>>> results() {
|
||||
return this.results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SearchResult<?, ?> that = (SearchResult<?, ?>) o;
|
||||
return Objects.equals(totalHitsCount, that.totalHitsCount) && Objects.equals(results, that.results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(totalHitsCount, results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", SearchResult.class.getSimpleName() + "[", "]")
|
||||
.add("totalHitsCount=" + totalHitsCount)
|
||||
.add("results=" + results)
|
||||
.toString();
|
||||
public Tuple2<Flux<SearchResultItem<T, U>>, Mono<Long>> splitShared() {
|
||||
Flux<LuceneSignal<SearchResultItem<T, U>>> shared = results.publish().refCount(2);
|
||||
return Tuples.of(
|
||||
shared.filter(LuceneSignal::isValue).map(LuceneSignal::getValue).share(),
|
||||
Mono.from(shared.filter(LuceneSignal::isTotalHitsCount).map(LuceneSignal::getTotalHitsCount)).cache()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +1,40 @@
|
||||
package it.cavallium.dbengine.client;
|
||||
|
||||
import it.cavallium.dbengine.database.collections.Joiner.ValueGetter;
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class SearchResultKeys<T> {
|
||||
|
||||
private final Mono<Long> totalHitsCount;
|
||||
private final Flux<SearchResultKey<T>> results;
|
||||
private final Flux<LuceneSignal<SearchResultKey<T>>> results;
|
||||
|
||||
public SearchResultKeys(Mono<Long> totalHitsCount, Flux<SearchResultKey<T>> results) {
|
||||
this.totalHitsCount = totalHitsCount;
|
||||
public SearchResultKeys(Flux<LuceneSignal<SearchResultKey<T>>> results) {
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public static <T, U> SearchResultKeys<T> empty() {
|
||||
return new SearchResultKeys<>(Mono.just(0L), Flux.empty());
|
||||
return new SearchResultKeys<>(Flux.just(LuceneSignal.totalHitsCount(0L)));
|
||||
}
|
||||
|
||||
public Mono<Long> totalHitsCount() {
|
||||
return this.totalHitsCount;
|
||||
}
|
||||
|
||||
public Flux<SearchResultKey<T>> results() {
|
||||
public Flux<LuceneSignal<SearchResultKey<T>>> results() {
|
||||
return this.results;
|
||||
}
|
||||
|
||||
public <U> SearchResult<T, U> withValues(ValueGetter<T, U> valuesGetter) {
|
||||
return new SearchResult<>(totalHitsCount,
|
||||
results.flatMap(item -> valuesGetter
|
||||
.get(item.getKey())
|
||||
.map(value -> new SearchResultItem<>(item.getKey(), value, item.getScore())))
|
||||
return new SearchResult<>(
|
||||
results.flatMapSequential(item -> {
|
||||
if (item.isValue()) {
|
||||
return valuesGetter
|
||||
.get(item.getValue().getKey())
|
||||
.map(value -> LuceneSignal.value(new SearchResultItem<>(item.getValue().getKey(), value, item.getValue().getScore())));
|
||||
} else {
|
||||
return Mono.just(item.mapTotalHitsCount());
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SearchResultKeys<?> that = (SearchResultKeys<?>) o;
|
||||
return Objects.equals(totalHitsCount, that.totalHitsCount) && Objects.equals(results, that.results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(totalHitsCount, results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", SearchResultKeys.class.getSimpleName() + "[", "]")
|
||||
.add("totalHitsCount=" + totalHitsCount)
|
||||
.add("results=" + results)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package it.cavallium.dbengine.database;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class LLKeyScore {
|
||||
public class LLKeyScore implements LLSignal {
|
||||
|
||||
private final String key;
|
||||
private final float score;
|
||||
@ -45,4 +45,24 @@ public class LLKeyScore {
|
||||
", score=" + score +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTotalHitsCount() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LLKeyScore getValue() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalHitsCount() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
@ -49,9 +49,11 @@ public interface LLLuceneIndex extends LLSnapshottable {
|
||||
|
||||
default Mono<Long> count(@Nullable LLSnapshot snapshot, Query query) {
|
||||
QueryParams params = QueryParams.of(query, 0, Nullablefloat.empty(), NoSort.of(), ScoreMode.of(false, false));
|
||||
return this.search(snapshot, params, null)
|
||||
.flatMap(LLSearchResult::totalHitsCount)
|
||||
.single();
|
||||
return Mono.from(this.search(snapshot, params, null)
|
||||
.flatMapMany(LLSearchResult::results)
|
||||
.flatMap(s -> s)
|
||||
.filter(LLSignal::isTotalHitsCount)
|
||||
.map(LLSignal::getTotalHitsCount));
|
||||
}
|
||||
|
||||
boolean isLowMemoryMode();
|
||||
|
@ -8,33 +8,22 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
public class LLSearchResult {
|
||||
|
||||
private final Mono<Long> totalHitsCount;
|
||||
private final Flux<Flux<LLKeyScore>> results;
|
||||
private final Flux<Flux<LLSignal>> results;
|
||||
|
||||
public LLSearchResult(Mono<Long> totalHitsCount, Flux<Flux<LLKeyScore>> results) {
|
||||
this.totalHitsCount = totalHitsCount;
|
||||
public LLSearchResult(Flux<Flux<LLSignal>> results) {
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public static LLSearchResult empty() {
|
||||
return new LLSearchResult(Mono.just(0L), Flux.just(Flux.empty()));
|
||||
return new LLSearchResult(Flux.just(Flux.just(new LLTotalHitsCount(0L))));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static BiFunction<LLSearchResult, LLSearchResult, LLSearchResult> accumulator() {
|
||||
return (a, b) -> {
|
||||
var mergedTotals = a.totalHitsCount.flatMap(aL -> b.totalHitsCount.map(bL -> aL + bL));
|
||||
var mergedResults = Flux.merge(a.results, b.results);
|
||||
|
||||
return new LLSearchResult(mergedTotals, mergedResults);
|
||||
};
|
||||
return (a, b) -> new LLSearchResult(Flux.merge(a.results, b.results));
|
||||
}
|
||||
|
||||
public Mono<Long> totalHitsCount() {
|
||||
return this.totalHitsCount;
|
||||
}
|
||||
|
||||
public Flux<Flux<LLKeyScore>> results() {
|
||||
public Flux<Flux<LLSignal>> results() {
|
||||
return this.results;
|
||||
}
|
||||
|
||||
@ -50,11 +39,6 @@ public class LLSearchResult {
|
||||
return false;
|
||||
}
|
||||
final LLSearchResult other = (LLSearchResult) o;
|
||||
final Object this$totalHitsCount = this.totalHitsCount();
|
||||
final Object other$totalHitsCount = other.totalHitsCount();
|
||||
if (!Objects.equals(this$totalHitsCount, other$totalHitsCount)) {
|
||||
return false;
|
||||
}
|
||||
final Object this$results = this.results();
|
||||
final Object other$results = other.results();
|
||||
return Objects.equals(this$results, other$results);
|
||||
@ -63,14 +47,12 @@ public class LLSearchResult {
|
||||
public int hashCode() {
|
||||
final int PRIME = 59;
|
||||
int result = 1;
|
||||
final Object $totalHitsCount = this.totalHitsCount();
|
||||
result = result * PRIME + ($totalHitsCount == null ? 43 : $totalHitsCount.hashCode());
|
||||
final Object $results = this.results();
|
||||
result = result * PRIME + ($results == null ? 43 : $results.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "LLSearchResult(totalHitsCount=" + this.totalHitsCount() + ", results=" + this.results() + ")";
|
||||
return "LLSearchResult(results=" + this.results() + ")";
|
||||
}
|
||||
}
|
||||
|
20
src/main/java/it/cavallium/dbengine/database/LLSignal.java
Normal file
20
src/main/java/it/cavallium/dbengine/database/LLSignal.java
Normal file
@ -0,0 +1,20 @@
|
||||
package it.cavallium.dbengine.database;
|
||||
|
||||
public interface LLSignal {
|
||||
|
||||
boolean isValue();
|
||||
|
||||
boolean isTotalHitsCount();
|
||||
|
||||
LLKeyScore getValue();
|
||||
|
||||
long getTotalHitsCount();
|
||||
|
||||
static LLSignal value(LLKeyScore value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
static LLTotalHitsCount totalHitsCount(long totalHitsCount) {
|
||||
return new LLTotalHitsCount(totalHitsCount);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package it.cavallium.dbengine.database;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class LLTotalHitsCount implements LLSignal {
|
||||
|
||||
private final long value;
|
||||
|
||||
public LLTotalHitsCount(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTotalHitsCount() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public LLKeyScore getValue() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalHitsCount() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
LLTotalHitsCount that = (LLTotalHitsCount) o;
|
||||
return value == that.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", LLTotalHitsCount.class.getSimpleName() + "[", "]").add("count=" + value).toString();
|
||||
}
|
||||
}
|
@ -9,8 +9,10 @@ import it.cavallium.dbengine.database.LLKeyScore;
|
||||
import it.cavallium.dbengine.database.LLLuceneIndex;
|
||||
import it.cavallium.dbengine.database.LLSearchCollectionStatisticsGetter;
|
||||
import it.cavallium.dbengine.database.LLSearchResult;
|
||||
import it.cavallium.dbengine.database.LLSignal;
|
||||
import it.cavallium.dbengine.database.LLSnapshot;
|
||||
import it.cavallium.dbengine.database.LLTerm;
|
||||
import it.cavallium.dbengine.database.LLTotalHitsCount;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import it.cavallium.dbengine.lucene.LuceneUtils;
|
||||
import it.cavallium.dbengine.lucene.ScheduledTaskLifecycle;
|
||||
@ -29,7 +31,9 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@ -57,11 +61,6 @@ import org.warp.commonutils.log.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.GroupedFlux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks;
|
||||
import reactor.core.publisher.Sinks.EmissionException;
|
||||
import reactor.core.publisher.Sinks.EmitResult;
|
||||
import reactor.core.publisher.Sinks.Many;
|
||||
import reactor.core.publisher.Sinks.One;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import reactor.util.function.Tuple2;
|
||||
@ -546,77 +545,82 @@ public class LLLocalLuceneIndex implements LLLuceneIndex {
|
||||
Query luceneQuery,
|
||||
Sort luceneSort,
|
||||
ScoreMode luceneScoreMode) {
|
||||
var searchFlux = Flux.<LLSignal>create(sink -> {
|
||||
AtomicBoolean cancelled = new AtomicBoolean();
|
||||
AtomicLong requests = new AtomicLong();
|
||||
Semaphore requestsAvailable = new Semaphore(0);
|
||||
sink.onDispose(() -> {
|
||||
cancelled.set(true);
|
||||
requestsAvailable.release();
|
||||
});
|
||||
sink.onRequest(delta -> {
|
||||
requests.addAndGet(delta);
|
||||
requestsAvailable.release();
|
||||
});
|
||||
|
||||
One<Long> totalHitsCountSink = Sinks.one();
|
||||
Many<LLKeyScore> topKeysSink = Sinks
|
||||
.many()
|
||||
.unicast()
|
||||
.onBackpressureBuffer();
|
||||
|
||||
var searchFlux = Mono.<Void>create(sink -> {
|
||||
try {
|
||||
if (doDistributedPre) {
|
||||
allowOnlyQueryParsingCollectorStreamSearcher.search(indexSearcher, luceneQuery);
|
||||
totalHitsCountSink.tryEmitValue(0L);
|
||||
} else {
|
||||
int boundedLimit = Math.max(0, limit > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) limit);
|
||||
streamSearcher.search(indexSearcher,
|
||||
luceneQuery,
|
||||
boundedLimit,
|
||||
luceneSort,
|
||||
luceneScoreMode,
|
||||
minCompetitiveScore,
|
||||
keyFieldName,
|
||||
keyScore -> {
|
||||
EmitResult result = topKeysSink.tryEmitNext(fixKeyScore(keyScore, scoreDivisor));
|
||||
if (result.isSuccess()) {
|
||||
return HandleResult.CONTINUE;
|
||||
} else {
|
||||
if (result == EmitResult.FAIL_CANCELLED) {
|
||||
logger.debug("Fail to emit next value: cancelled");
|
||||
//noinspection BlockingMethodInNonBlockingContext
|
||||
requestsAvailable.acquire();
|
||||
requestsAvailable.release();
|
||||
if (!cancelled.get()) {
|
||||
if (doDistributedPre) {
|
||||
//noinspection BlockingMethodInNonBlockingContext
|
||||
allowOnlyQueryParsingCollectorStreamSearcher.search(indexSearcher, luceneQuery);
|
||||
sink.next(new LLTotalHitsCount(0L));
|
||||
} else {
|
||||
int boundedLimit = Math.max(0, limit > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) limit);
|
||||
//noinspection BlockingMethodInNonBlockingContext
|
||||
streamSearcher.search(indexSearcher,
|
||||
luceneQuery,
|
||||
boundedLimit,
|
||||
luceneSort,
|
||||
luceneScoreMode,
|
||||
minCompetitiveScore,
|
||||
keyFieldName,
|
||||
keyScore -> {
|
||||
try {
|
||||
while (requests.get() <= 0 && !cancelled.get()) {
|
||||
requestsAvailable.acquire();
|
||||
}
|
||||
if (!cancelled.get()) {
|
||||
requests.decrementAndGet();
|
||||
sink.next(fixKeyScore(keyScore, scoreDivisor));
|
||||
return HandleResult.CONTINUE;
|
||||
} else {
|
||||
return HandleResult.HALT;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
sink.error(ex);
|
||||
cancelled.set(true);
|
||||
return HandleResult.HALT;
|
||||
} else if (result == EmitResult.FAIL_TERMINATED) {
|
||||
logger.debug("Fail to emit next value: terminated");
|
||||
return HandleResult.HALT;
|
||||
} else if (result == EmitResult.FAIL_ZERO_SUBSCRIBER) {
|
||||
logger.error("Fail to emit next value: zero subscriber. You must subscribe to results before total hits if you specified a limit > 0!");
|
||||
sink.error(new EmissionException(result));
|
||||
throw new EmissionException(result);
|
||||
} else {
|
||||
throw new EmissionException(result);
|
||||
}
|
||||
},
|
||||
totalHitsCount -> {
|
||||
try {
|
||||
while (requests.get() <= 0 && !cancelled.get()) {
|
||||
requestsAvailable.acquire();
|
||||
}
|
||||
if (!cancelled.get()) {
|
||||
requests.decrementAndGet();
|
||||
sink.next(new LLTotalHitsCount(totalHitsCount));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
sink.error(ex);
|
||||
cancelled.set(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
totalHitsCount -> {
|
||||
EmitResult result = totalHitsCountSink.tryEmitValue(totalHitsCount);
|
||||
if (result.isFailure()) {
|
||||
if (result == EmitResult.FAIL_CANCELLED) {
|
||||
logger.debug("Fail to emit total hits count: cancelled");
|
||||
} else if (result == EmitResult.FAIL_TERMINATED) {
|
||||
logger.debug("Fail to emit total hits count: terminated");
|
||||
} else if (result == EmitResult.FAIL_ZERO_SUBSCRIBER) {
|
||||
logger.debug("Fail to emit total hits count: zero subscriber");
|
||||
} else {
|
||||
sink.error(new EmissionException(result));
|
||||
throw new EmissionException(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
if (!cancelled.get()) {
|
||||
sink.complete();
|
||||
}
|
||||
}
|
||||
topKeysSink.tryEmitComplete();
|
||||
sink.success();
|
||||
} catch (IOException e) {
|
||||
topKeysSink.tryEmitError(e);
|
||||
totalHitsCountSink.tryEmitError(e);
|
||||
sink.error(e);
|
||||
} catch (Exception ex) {
|
||||
sink.error(ex);
|
||||
}
|
||||
}).subscribeOn(luceneQueryScheduler).cache();
|
||||
}).subscribeOn(luceneQueryScheduler);
|
||||
|
||||
return new LLSearchResult(
|
||||
Mono.<Long>firstWithValue(searchFlux.then(Mono.empty()), totalHitsCountSink.asMono()),
|
||||
Flux.<Flux<LLKeyScore>>merge(searchFlux.then(Mono.empty()), Flux.just(topKeysSink.asFlux()))
|
||||
);
|
||||
return new LLSearchResult(Flux.just(searchFlux));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -257,7 +257,7 @@ public class LLLocalMultiLuceneIndex implements LLLuceneIndex {
|
||||
var resultsWithTermination = result
|
||||
.results()
|
||||
.map(flux -> flux.doOnTerminate(() -> completedAction(actionId)));
|
||||
return new LLSearchResult(result.totalHitsCount(), resultsWithTermination);
|
||||
return new LLSearchResult(resultsWithTermination);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
@ -318,7 +318,7 @@ public class LLLocalMultiLuceneIndex implements LLLuceneIndex {
|
||||
var resultsWithTermination = result
|
||||
.results()
|
||||
.map(flux -> flux.doOnTerminate(() -> completedAction(actionId)));
|
||||
return new LLSearchResult(result.totalHitsCount(), resultsWithTermination);
|
||||
return new LLSearchResult(resultsWithTermination);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user