Skip to content

Commit

Permalink
feat: add query search operators to support more complex index matching
Browse files Browse the repository at this point in the history
  • Loading branch information
guqing committed Jan 12, 2024
1 parent 032f705 commit 8d96c20
Show file tree
Hide file tree
Showing 40 changed files with 1,529 additions and 414 deletions.
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
package run.halo.app.extension;

import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;

import java.util.Map;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.CollectionUtils;
import run.halo.app.extension.index.query.QueryFactory;
import run.halo.app.extension.router.selector.FieldSelector;
import run.halo.app.extension.router.selector.LabelSelector;

@Getter
@RequiredArgsConstructor
@Builder(builderMethodName = "internalBuilder")
public class DefaultExtensionMatcher implements ExtensionMatcher {
private static final SpelExpressionParser PARSER = new SpelExpressionParser();

private final ExtensionClient client;
private final GroupVersionKind gvk;
private final LabelSelector labelSelector;
private final FieldSelector fieldSelector;

public static DefaultExtensionMatcherBuilder builder(GroupVersionKind gvk) {
return internalBuilder().gvk(gvk);
public static DefaultExtensionMatcherBuilder builder(ExtensionClient client,
GroupVersionKind gvk) {
return internalBuilder().client(client).gvk(gvk);
}

/**
Expand All @@ -32,18 +30,28 @@ public static DefaultExtensionMatcherBuilder builder(GroupVersionKind gvk) {
*/
@Override
public boolean match(Extension extension) {
if (gvk != null && !gvk.equals(extension.groupVersionKind())) {
if (!gvk.equals(extension.groupVersionKind())) {
return false;
}
var labels = defaultIfNull(extension.getMetadata().getLabels(), Map.<String, String>of());
if (labelSelector != null && !labelSelector.test(labels)) {
return false;
if (!hasFieldSelector() && !hasLabelSelector()) {
return true;
}

if (fieldSelector != null) {
return fieldSelector.test(key -> PARSER.parseRaw(key)
.getValue(extension, String.class));
var listOptions = new ListOptions();
listOptions.setLabelSelector(labelSelector);
var fieldQuery = QueryFactory.all();
if (hasFieldSelector()) {
fieldQuery = QueryFactory.and(fieldQuery, fieldSelector.query());
}
return true;
listOptions.setFieldSelector(new FieldSelector(fieldQuery));
return client.indexedQueryEngine().retrieve(getGvk(),
listOptions, PageRequestImpl.ofSize(1)).getTotal() > 0;
}

boolean hasFieldSelector() {
return fieldSelector != null && fieldSelector.query() != null;
}

boolean hasLabelSelector() {
return labelSelector != null && !CollectionUtils.isEmpty(labelSelector.getMatchers());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

import java.util.Objects;
import lombok.Builder;
import lombok.Getter;
import org.springframework.util.Assert;

public class WatcherExtensionMatchers {
@Getter
private final ExtensionClient client;
private final GroupVersionKind gvk;
private final ExtensionMatcher onAddMatcher;
private final ExtensionMatcher onUpdateMatcher;
Expand All @@ -14,12 +18,19 @@ public class WatcherExtensionMatchers {
* {@link DefaultExtensionMatcher}.
*/
@Builder(builderMethodName = "internalBuilder")
public WatcherExtensionMatchers(GroupVersionKind gvk, ExtensionMatcher onAddMatcher,
public WatcherExtensionMatchers(ExtensionClient client,
GroupVersionKind gvk, ExtensionMatcher onAddMatcher,
ExtensionMatcher onUpdateMatcher, ExtensionMatcher onDeleteMatcher) {
Assert.notNull(client, "The client must not be null.");
Assert.notNull(gvk, "The gvk must not be null.");
this.client = client;
this.gvk = gvk;
this.onAddMatcher = Objects.requireNonNullElse(onAddMatcher, emptyMatcher(gvk));
this.onUpdateMatcher = Objects.requireNonNullElse(onUpdateMatcher, emptyMatcher(gvk));
this.onDeleteMatcher = Objects.requireNonNullElse(onDeleteMatcher, emptyMatcher(gvk));
this.onAddMatcher =
Objects.requireNonNullElse(onAddMatcher, emptyMatcher(client, gvk));
this.onUpdateMatcher =
Objects.requireNonNullElse(onUpdateMatcher, emptyMatcher(client, gvk));
this.onDeleteMatcher =
Objects.requireNonNullElse(onDeleteMatcher, emptyMatcher(client, gvk));
}

public GroupVersionKind getGroupVersionKind() {
Expand All @@ -38,11 +49,13 @@ public ExtensionMatcher onDeleteMatcher() {
return this.onDeleteMatcher;
}

public static WatcherExtensionMatchersBuilder builder(GroupVersionKind gvk) {
return internalBuilder().gvk(gvk);
public static WatcherExtensionMatchersBuilder builder(ExtensionClient client,
GroupVersionKind gvk) {
return internalBuilder().gvk(gvk).client(client);
}

static ExtensionMatcher emptyMatcher(GroupVersionKind gvk) {
return DefaultExtensionMatcher.builder(gvk).build();
static ExtensionMatcher emptyMatcher(ExtensionClient client,
GroupVersionKind gvk) {
return DefaultExtensionMatcher.builder(client, gvk).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ public Controller build() {
Assert.notNull(reconciler, "Reconciler must not be null");

var queue = new DefaultQueue<Request>(nowSupplier, minDelay);
var extensionMatchers = WatcherExtensionMatchers.builder(extension.groupVersionKind())
var extensionMatchers = WatcherExtensionMatchers.builder(client,
extension.groupVersionKind())
.onAddMatcher(onAddMatcher)
.onUpdateMatcher(onUpdateMatcher)
.onDeleteMatcher(onDeleteMatcher)
Expand Down
15 changes: 15 additions & 0 deletions api/src/main/java/run/halo/app/extension/index/query/All.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package run.halo.app.extension.index.query;

import java.util.NavigableSet;

public class All extends SimpleQuery {

public All(String fieldName) {
super(fieldName, null);
}

@Override
public NavigableSet<String> matches(QueryIndexView indexView) {
return indexView.getAllIdsForField(fieldName);
}
}
37 changes: 37 additions & 0 deletions api/src/main/java/run/halo/app/extension/index/query/And.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package run.halo.app.extension.index.query;

import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.NavigableSet;

public class And extends LogicalQuery {

/**
* Creates a new And query with the given child queries.
*
* @param childQueries The child queries
*/
public And(Collection<Query> childQueries) {
super(childQueries);
if (this.size < 2) {
throw new IllegalStateException(
"An 'And' query cannot have fewer than 2 child queries, " + childQueries.size()
+ " were supplied");
}
}

@Override
public NavigableSet<String> matches(QueryIndexView indexView) {
NavigableSet<String> resultSet = null;
for (Query query : childQueries) {
NavigableSet<String> currentResult = query.matches(indexView);
indexView.removeAllFieldValuesByIdNotIn(currentResult);
if (resultSet == null) {
resultSet = Sets.newTreeSet(currentResult);
} else {
resultSet.retainAll(currentResult);
}
}
return resultSet == null ? Sets.newTreeSet() : resultSet;
}
}
35 changes: 35 additions & 0 deletions api/src/main/java/run/halo/app/extension/index/query/Between.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package run.halo.app.extension.index.query;

import com.google.common.collect.Sets;
import java.util.NavigableSet;

public class Between extends SimpleQuery {
private final String lowerValue;
private final boolean lowerInclusive;
private final String upperValue;
private final boolean upperInclusive;

public Between(String fieldName, String lowerValue, boolean lowerInclusive,
String upperValue, boolean upperInclusive) {
// value and isFieldRef are not used in Between
super(fieldName, null, false);
this.lowerValue = lowerValue;
this.lowerInclusive = lowerInclusive;
this.upperValue = upperValue;
this.upperInclusive = upperInclusive;
}


@Override
public NavigableSet<String> matches(QueryIndexView indexView) {
NavigableSet<String> allValues = indexView.getAllValuesForField(fieldName);
// get all values in the specified range
var subSet = allValues.subSet(lowerValue, lowerInclusive, upperValue, upperInclusive);

var resultSet = Sets.<String>newTreeSet();
for (String val : subSet) {
resultSet.addAll(indexView.getIdsForFieldValue(fieldName, val));
}
return resultSet;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package run.halo.app.extension.index.query;

import java.util.NavigableSet;

public class EqualQuery extends SimpleQuery {

public EqualQuery(String fieldName, String value) {
super(fieldName, value);
}

public EqualQuery(String fieldName, String value, boolean isFieldRef) {
super(fieldName, value, isFieldRef);
}

@Override
public NavigableSet<String> matches(QueryIndexView indexView) {
if (isFieldRef) {
return resultSetForRefValue(indexView);
}
return resultSetForExactValue(indexView);
}

private NavigableSet<String> resultSetForRefValue(QueryIndexView indexView) {
return indexView.findIdsForFieldValueEqual(fieldName, value);
}

private NavigableSet<String> resultSetForExactValue(QueryIndexView indexView) {
return indexView.getIdsForFieldValue(fieldName, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package run.halo.app.extension.index.query;

import com.google.common.collect.Sets;
import java.util.NavigableSet;

public class GreaterThanQuery extends SimpleQuery {
private final boolean orEqual;

public GreaterThanQuery(String fieldName, String value, boolean orEqual) {
this(fieldName, value, orEqual, false);
}

public GreaterThanQuery(String fieldName, String value, boolean orEqual, boolean isFieldRef) {
super(fieldName, value, isFieldRef);
this.orEqual = orEqual;
}

@Override
public NavigableSet<String> matches(QueryIndexView indexView) {
if (isFieldRef) {
return resultSetForRefValue(indexView);
}
return resultSetForExtractValue(indexView);
}

private NavigableSet<String> resultSetForRefValue(QueryIndexView indexView) {
return indexView.findIdsForFieldValueGreaterThan(fieldName, value, orEqual);
}

private NavigableSet<String> resultSetForExtractValue(QueryIndexView indexView) {
var resultSet = Sets.<String>newTreeSet();
var allValues = indexView.getAllValuesForField(fieldName);
NavigableSet<String> tailSet =
orEqual ? allValues.tailSet(value, true) : allValues.tailSet(value, false);

for (String val : tailSet) {
resultSet.addAll(indexView.getIdsForFieldValue(fieldName, val));
}
return resultSet;
}
}
23 changes: 23 additions & 0 deletions api/src/main/java/run/halo/app/extension/index/query/InQuery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package run.halo.app.extension.index.query;

import com.google.common.collect.Sets;
import java.util.NavigableSet;
import java.util.Set;

public class InQuery extends SimpleQuery {
private final Set<String> values;

public InQuery(String columnName, Set<String> values) {
super(columnName, null);
this.values = values;
}

@Override
public NavigableSet<String> matches(QueryIndexView indexView) {
NavigableSet<String> resultSet = Sets.newTreeSet();
for (String val : values) {
resultSet.addAll(indexView.getIdsForFieldValue(fieldName, val));
}
return resultSet;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package run.halo.app.extension.index.query;

import com.google.common.collect.Sets;
import java.util.NavigableSet;

public class LessThanQuery extends SimpleQuery {
private final boolean orEqual;

public LessThanQuery(String fieldName, String value, boolean orEqual) {
this(fieldName, value, orEqual, false);
}

public LessThanQuery(String fieldName, String value, boolean orEqual, boolean isFieldRef) {
super(fieldName, value, isFieldRef);
this.orEqual = orEqual;
}

@Override
public NavigableSet<String> matches(QueryIndexView indexView) {
if (isFieldRef) {
return resultSetForRefValue(indexView);
}
return resultSetForExactValue(indexView);
}

private NavigableSet<String> resultSetForRefValue(QueryIndexView indexView) {
return indexView.findIdsForFieldValueLessThan(fieldName, value, orEqual);
}

private NavigableSet<String> resultSetForExactValue(QueryIndexView indexView) {
var resultSet = Sets.<String>newTreeSet();
var allValues = indexView.getAllValuesForField(fieldName);
var headSet = orEqual ? allValues.headSet(value, true)
: allValues.headSet(value, false);

for (String val : headSet) {
resultSet.addAll(indexView.getIdsForFieldValue(fieldName, val));
}
return resultSet;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package run.halo.app.extension.index.query;

import java.util.Collection;
import java.util.Objects;

public abstract class LogicalQuery implements Query {
protected final Collection<Query> childQueries;
protected final int size;

/**
* Creates a new logical query with the given child queries.
*
* @param childQueries with the given child queries.
*/
public LogicalQuery(Collection<Query> childQueries) {
Objects.requireNonNull(childQueries,
"The child queries supplied to a logical query cannot be null");
for (Query query : childQueries) {
if (!isValid(query)) {
throw new IllegalStateException("Unexpected type of query: " + (query == null ? null
: query + ", " + query.getClass()));
}
}
this.size = childQueries.size();
this.childQueries = childQueries;
}

boolean isValid(Query query) {
return query instanceof LogicalQuery || query instanceof SimpleQuery;
}
}
Loading

0 comments on commit 8d96c20

Please sign in to comment.