Skip to content

Commit

Permalink
Disable evaluation for grouping function queries under certain circum…
Browse files Browse the repository at this point in the history
…stances

Feature flag disabling evaluation for queries with groupby functions
  • Loading branch information
apmoriarty committed Jan 22, 2025
1 parent 9776389 commit 66b1fde
Show file tree
Hide file tree
Showing 4 changed files with 388 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package datawave.query.jexl.visitors;

import java.util.Set;

import org.apache.commons.jexl3.parser.ASTAndNode;
import org.apache.commons.jexl3.parser.ASTEQNode;
import org.apache.commons.jexl3.parser.ASTERNode;
import org.apache.commons.jexl3.parser.ASTFunctionNode;
import org.apache.commons.jexl3.parser.ASTGENode;
import org.apache.commons.jexl3.parser.ASTGTNode;
import org.apache.commons.jexl3.parser.ASTLENode;
import org.apache.commons.jexl3.parser.ASTLTNode;
import org.apache.commons.jexl3.parser.ASTNENode;
import org.apache.commons.jexl3.parser.ASTNRNode;
import org.apache.commons.jexl3.parser.ASTOrNode;
import org.apache.commons.jexl3.parser.JexlNode;

import datawave.query.jexl.JexlASTHelper;
import datawave.query.jexl.functions.FunctionJexlNodeVisitor;
import datawave.query.jexl.nodes.QueryPropertyMarker;

/**
* Determines if a query can disable evaluation when a grouping function is present
* <p>
* Evaluation cannot be disabled if any content, query, or filter functions exist, or if delayed or evaluation only markers are present.
*/
public class DisableEvaluationForGroupingVisitor extends ShortCircuitBaseVisitor {

private boolean canDisableEvaluation = true;

private final Set<String> indexedFields;
private final Set<String> indexOnlyFields;

/**
* Public entrypoint, this visitor requires the full set of indexed and index only fields
*
* @param node
* the JexlNode
* @param indexedFields
* the set of indexed fields
* @param indexOnlyFields
* the set of index only fields
* @return true if evaluation can be disabled
*/
public static boolean canDisableEvaluation(JexlNode node, Set<String> indexedFields, Set<String> indexOnlyFields) {
DisableEvaluationForGroupingVisitor visitor = new DisableEvaluationForGroupingVisitor(indexedFields, indexOnlyFields);
node.jjtAccept(visitor, null);
return visitor.canDisableEvaluation;
}

/**
* Private constructor to force static access
*/
private DisableEvaluationForGroupingVisitor(Set<String> indexedFields, Set<String> indexOnlyFields) {
this.indexedFields = indexedFields;
this.indexOnlyFields = indexOnlyFields;
}

@Override
public Object visit(ASTAndNode node, Object data) {
if (!canDisableEvaluation) {
return data;
}

QueryPropertyMarker.Instance instance = QueryPropertyMarker.findInstance(node);
if (instance.isAnyType()) {
switch (instance.getType()) {
case EVALUATION_ONLY:
case DELAYED:
canDisableEvaluation = false;
return data;
case BOUNDED_RANGE:
case EXCEEDED_OR:
case EXCEEDED_TERM:
case EXCEEDED_VALUE:
// continue recursing
break;
default:
// query planner should handled value, range terms
return data;
}
}

node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTOrNode node, Object data) {
if (!canDisableEvaluation) {
return data;
}

node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTNENode node, Object data) {
if (!canDisableEvaluation || !isFieldIndexed(node)) {
return data;
}
node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTEQNode node, Object data) {
if (!canDisableEvaluation || !isFieldIndexed(node)) {
return data;
}

node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTLTNode node, Object data) {
if (!canDisableEvaluation || !isFieldIndexed(node)) {
return data;
}

node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTGTNode node, Object data) {
if (!canDisableEvaluation || !isFieldIndexed(node)) {
return data;
}

node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTLENode node, Object data) {
if (!canDisableEvaluation || !isFieldIndexed(node)) {
return data;
}

node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTGENode node, Object data) {
if (!canDisableEvaluation || !isFieldIndexed(node)) {
return data;
}

node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTERNode node, Object data) {
if (!canDisableEvaluation || !isFieldIndexed(node)) {
return data;
}

node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTNRNode node, Object data) {
if (!canDisableEvaluation || !isFieldIndexed(node)) {
return data;
}

node.childrenAccept(this, data);
return data;
}

@Override
public Object visit(ASTFunctionNode node, Object data) {
if (!canDisableEvaluation) {
return data;
}

FunctionJexlNodeVisitor visitor = new FunctionJexlNodeVisitor();
visitor.visit(node, data);

switch (visitor.namespace()) {
case "filter":
case "content":
case "f":
// must evaluate for these functions
canDisableEvaluation = false;
return data;
}

node.childrenAccept(this, data);
return data;
}

private boolean isFieldIndexed(JexlNode node) {
String field = JexlASTHelper.getIdentifier(node);
if (field == null) {
return false;
}

Object literal = JexlASTHelper.getLiteralValue(node);
if (literal == null) {
// term like (FIELD == null) requires an evaluation
canDisableEvaluation = false;
return false;
}

boolean indexed = indexedFields.contains(field) || indexOnlyFields.contains(field);

if (!indexed) {
canDisableEvaluation = false;
}

return indexed;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@
import datawave.query.CloseableIterable;
import datawave.query.Constants;
import datawave.query.QueryParameters;
import datawave.query.attributes.ExcerptFields;
import datawave.query.attributes.UniqueFields;
import datawave.query.common.grouping.GroupFields;
import datawave.query.composite.CompositeMetadata;
import datawave.query.composite.CompositeUtils;
import datawave.query.config.ScanHintRule;
Expand Down Expand Up @@ -106,9 +103,9 @@
import datawave.query.jexl.visitors.AddShardsAndDaysVisitor;
import datawave.query.jexl.visitors.BoundedRangeDetectionVisitor;
import datawave.query.jexl.visitors.BoundedRangeIndexExpansionVisitor;
import datawave.query.jexl.visitors.CaseSensitivityVisitor;
import datawave.query.jexl.visitors.ConjunctionEliminationVisitor;
import datawave.query.jexl.visitors.DepthVisitor;
import datawave.query.jexl.visitors.DisableEvaluationForGroupingVisitor;
import datawave.query.jexl.visitors.DisjunctionEliminationVisitor;
import datawave.query.jexl.visitors.ExecutableDeterminationVisitor;
import datawave.query.jexl.visitors.ExecutableDeterminationVisitor.STATE;
Expand Down Expand Up @@ -138,7 +135,6 @@
import datawave.query.jexl.visitors.PushdownMissingIndexRangeNodesVisitor;
import datawave.query.jexl.visitors.PushdownUnexecutableNodesVisitor;
import datawave.query.jexl.visitors.QueryFieldsVisitor;
import datawave.query.jexl.visitors.QueryModelVisitor;
import datawave.query.jexl.visitors.QueryOptionsFromQueryVisitor;
import datawave.query.jexl.visitors.QueryPropertyMarkerSourceConsolidator;
import datawave.query.jexl.visitors.QueryPruningVisitor;
Expand All @@ -159,7 +155,6 @@
import datawave.query.jexl.visitors.ValidateFilterFunctionVisitor;
import datawave.query.jexl.visitors.order.OrderByCostVisitor;
import datawave.query.jexl.visitors.whindex.WhindexVisitor;
import datawave.query.language.functions.jexl.Unique;
import datawave.query.model.QueryModel;
import datawave.query.planner.async.AbstractQueryPlannerCallable;
import datawave.query.planner.async.FetchCompositeMetadata;
Expand Down Expand Up @@ -353,6 +348,10 @@ public class DefaultQueryPlanner extends QueryPlanner implements Cloneable {
* performance impact.
*/
protected boolean showReducedQueryPrune = true;
/**
* Feature flag to attempt disabling evaluation under certain circumstances when a query contains a grouping function
*/
protected boolean disableGroupByEvaluation = false;

// handles boilerplate operations that surround a visitor's execution (e.g., timers, logging, validating)
private TimedVisitorManager visitorManager = new TimedVisitorManager();
Expand Down Expand Up @@ -562,6 +561,16 @@ protected CloseableIterable<QueryData> process(ScannerFactory scannerFactory, Me
cfg = getQueryIterator(metadataHelper, config, "", false, false);
}
configureIterator(config, cfg, newQueryString, isFullTable);

// check for the case where evaluation can be disabled due to the presence of Grouping functions
// but only if query functions and content functions are absent as well
if (!config.getFullTableScanEnabled() && config.getGroupFields().hasGroupByFields()) {
boolean canDisable = DisableEvaluationForGroupingVisitor.canDisableEvaluation(config.getQueryTree(), getIndexedFields(), getIndexOnlyFields());
if (canDisable) {
config.setDisableEvaluation(true);
addOption(cfg, QueryOptions.DISABLE_EVALUATION, "true", false);
}
}
}

final QueryData queryData = new QueryData().withQuery(newQueryString).withSettings(Lists.newArrayList(cfg));
Expand Down Expand Up @@ -3449,4 +3458,12 @@ public int getConcurrentTimeoutMillis() {
public void setConcurrentTimeoutMillis(int concurrentTimeoutMillis) {
this.concurrentTimeoutMillis = concurrentTimeoutMillis;
}

public boolean getDisableGroupByEvaluation() {
return disableGroupByEvaluation;
}

public void setDisableGroupByEvaluation(boolean disableGroupByEvaluation) {
this.disableGroupByEvaluation = disableGroupByEvaluation;
}
}
Loading

0 comments on commit 66b1fde

Please sign in to comment.