Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement query validation endpoint #2645

Merged
merged 22 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
08a8df4
Implement query validation endpoint
lbschanno Oct 9, 2024
bb0bc48
Fix failing unit test
lbschanno Nov 21, 2024
95d1558
Merge branch 'integration' into task/queryValidationEndpointImpl
SethSmucker Nov 21, 2024
a2d24a4
Fix javadoc
lbschanno Nov 22, 2024
c64b9d2
Make InvalidQuoteVisitor.INVALID_QUOTE a primitive
lbschanno Nov 22, 2024
1c4c31e
Suggested changes (#2656)
ivakegg Dec 6, 2024
7152709
Address review comments
lbschanno Dec 6, 2024
a62a886
Remove unused imports and add logging
lbschanno Dec 6, 2024
980a84c
Merge branch 'integration' into task/queryValidationEndpointImpl
lbschanno Dec 6, 2024
71ecfc3
Fix failing test
lbschanno Dec 6, 2024
9b3b52c
Merge branch 'integration' into task/queryValidationEndpointImpl
lbschanno Dec 9, 2024
2e310f5
Merge branch 'integration' into task/queryValidationEndpointImpl
ivakegg Jan 8, 2025
3fb97fa
Remove unecessary XMLElement annotation with duplicate name
lbschanno Jan 8, 2025
b5e8bbd
Merge branch 'integration' into task/queryValidationEndpointImpl
lbschanno Jan 8, 2025
811b894
Add required xml annotations to QueryValidationResponse.Result
lbschanno Jan 13, 2025
9076fdf
Ensure resources are released at the end of a call
lbschanno Jan 14, 2025
60bfa62
Use QUERY_VALIDATION_ERROR code
lbschanno Jan 14, 2025
fef4cba
Merge branch 'integration' into task/queryValidationEndpointImpl
ivakegg Jan 15, 2025
75c6f81
Remove unused import
lbschanno Jan 15, 2025
a607c25
Merge branch 'integration' into task/queryValidationEndpointImpl
ivakegg Jan 15, 2025
115221c
Add special quotes to Constants
lbschanno Jan 15, 2025
b6a3c8f
Merge branch 'integration' into task/queryValidationEndpointImpl
ivakegg Jan 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions core/query/src/main/java/datawave/core/query/logic/QueryLogic.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import org.apache.accumulo.core.client.AccumuloClient;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.iterators.TransformIterator;

import datawave.audit.SelectorExtractor;
Expand All @@ -24,6 +25,7 @@
import datawave.webservice.query.exception.QueryException;
import datawave.webservice.query.result.event.ResponseObjectFactory;
import datawave.webservice.result.BaseResponse;
import datawave.webservice.result.QueryValidationResponse;

public interface QueryLogic<T> extends Iterable<T>, Cloneable, ParameterValidator {

Expand Down Expand Up @@ -481,4 +483,28 @@ default void preInitialize(Query settings, Set<Authorizations> userAuthorization

void setServerUser(ProxiedUserDetails serverUser);

/**
* Validates the given query according to the validation criteria established for the query logic.
*
* @param client
* the Accumulo connector to use for this query
* @param query
* the query settings (query, begin date, end date, etc.)
* @param auths
* the authorizations that have been calculated for this query based on the caller and server.
* @return a list of messages detailing any issues found for the query
*/
default Object validateQuery(AccumuloClient client, Query query, Set<Authorizations> auths) throws Exception {
throw new UnsupportedOperationException("Query validation not implemented");
}

/**
* Return a transformer that will convert the result of {@link QueryLogic#validateQuery(AccumuloClient, Query, Set)} to a {@link QueryValidationResponse}.
*
* @return the transformer
*/
default Transformer<Object,QueryValidationResponse> getQueryValidationResponseTransformer() {
throw new UnsupportedOperationException("Query validation response transformer not implemented");
}

}
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1584,7 +1584,7 @@
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<artifactId>junit-jupiter</artifactId>
<version>${version.junit.bom}</version>
</dependency>
<dependency>
Expand Down Expand Up @@ -1694,7 +1694,7 @@
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<artifactId>junit-jupiter</artifactId>
<version>${version.junit.bom}</version>
</dependency>
<dependency>
Expand Down
5 changes: 5 additions & 0 deletions warehouse/query-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,11 @@
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class Constants {

public static final String PIPE = "|";

public static final String ASTERISK = "*";

public static final Text TEXT_NULL = new Text(NULL);

public static final Text FI_PREFIX = new Text("fi");
Expand Down Expand Up @@ -97,4 +99,11 @@ public class Constants {
public static final String END_DATE = "end.date";

public static final String COLUMN_VISIBILITY = "columnVisibility";

public static final Character BACKSLASH_CHAR = '\\';
public static final Character ASTERISK_CHAR = '*';

public static final String JEXL = "JEXL";
public static final String LUCENE = "LUCENE";
public static final String LUCENE_UUID = "LUCENE-UUID";
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ public Set<String> fieldsForNormalization(MetadataHelper helper, Set<String> dat
public Set<String> fields(MetadataHelper helper, Set<String> datatypeFilter) {
FunctionJexlNodeVisitor functionMetadata = new FunctionJexlNodeVisitor();
node.jjtAccept(functionMetadata, null);
Set<String> fields = Sets.newHashSet();
// Maintain insertion order.
Set<String> fields = Sets.newLinkedHashSet();

List<JexlNode> arguments = functionMetadata.args();
if (MATCHCOUNTOF.equals(functionMetadata.name())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.apache.commons.jexl3.parser.JexlNodes;
import org.apache.commons.jexl3.parser.ParserTreeConstants;

import datawave.query.Constants;
import datawave.query.attributes.AttributeFactory;
import datawave.query.attributes.UniqueFields;
import datawave.query.config.ShardQueryConfiguration;
Expand All @@ -29,9 +30,11 @@
import datawave.query.jexl.functions.arguments.JexlArgumentDescriptor;
import datawave.query.jexl.nodes.QueryPropertyMarker;
import datawave.query.jexl.visitors.EventDataQueryExpressionVisitor;
import datawave.query.jexl.visitors.PrintingVisitor;
lbschanno marked this conversation as resolved.
Show resolved Hide resolved
import datawave.query.jexl.visitors.QueryOptionsFromQueryVisitor;
import datawave.query.util.DateIndexHelper;
import datawave.query.util.MetadataHelper;
import datawave.util.StringUtils;

public class QueryFunctionsDescriptor implements JexlFunctionArgumentDescriptorFactory {

Expand Down Expand Up @@ -152,6 +155,15 @@ public Set<String> fields(MetadataHelper helper, Set<String> datatypeFilter) {
case QueryFunctions.NO_EXPANSION:
case QueryFunctions.LENIENT_FIELDS_FUNCTION:
case QueryFunctions.STRICT_FIELDS_FUNCTION:
case QueryFunctions.EXCERPT_FIELDS_FUNCTION:
case QueryOptionsFromQueryVisitor.UniqueFunction.UNIQUE_BY_YEAR_FUNCTION:
case QueryOptionsFromQueryVisitor.UniqueFunction.UNIQUE_BY_MONTH_FUNCTION:
case QueryOptionsFromQueryVisitor.UniqueFunction.UNIQUE_BY_DAY_FUNCTION:
case QueryOptionsFromQueryVisitor.UniqueFunction.UNIQUE_BY_HOUR_FUNCTION:
case QueryOptionsFromQueryVisitor.UniqueFunction.UNIQUE_BY_TENTH_OF_HOUR_FUNCTION:
case QueryOptionsFromQueryVisitor.UniqueFunction.UNIQUE_BY_MINUTE_FUNCTION:
case QueryOptionsFromQueryVisitor.UniqueFunction.UNIQUE_BY_SECOND_FUNCTION:
case QueryOptionsFromQueryVisitor.UniqueFunction.UNIQUE_BY_MILLISECOND_FUNCTION:
// In practice each of these functions should be parsed from the query
// almost immediately. This implementation is added for consistency
for (JexlNode arg : args) {
Expand Down Expand Up @@ -181,6 +193,12 @@ public Set<String> fields(MetadataHelper helper, Set<String> datatypeFilter) {
}
}
break;
case QueryFunctions.RENAME_FUNCTION:
for (JexlNode arg : args) {
String value = JexlNodes.getIdentifierOrLiteralAsString(arg);
String[] parts = StringUtils.split(value, Constants.EQUALS);
fields.add(parts[0]);
}
case QueryFunctions.SUMMARY_FUNCTION:
break;
case QueryFunctions.MATCH_REGEX:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package datawave.query.jexl.visitors;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;

import org.apache.commons.jexl3.parser.ASTFunctionNode;
import org.apache.commons.jexl3.parser.JexlNode;
import org.apache.commons.lang3.tuple.Pair;

import com.google.common.collect.LinkedHashMultimap;

import datawave.query.jexl.functions.FunctionJexlNodeVisitor;
import datawave.query.jexl.functions.JexlFunctionArgumentDescriptorFactory;
import datawave.query.jexl.functions.arguments.JexlArgumentDescriptor;
import datawave.query.util.MetadataHelper;

/**
* A visitor that fetches all fields from the specified functions.
*/
public class FetchFunctionFieldsVisitor extends ShortCircuitBaseVisitor {

private final Set<Pair<String,String>> functions;
private final MetadataHelper metadataHelper;
// Maintain insertion order.
private final LinkedHashMultimap<Pair<String,String>,String> fields = LinkedHashMultimap.create();

/**
* Fetch the fields seen in the specified functions.
*
* @param query
* the query tree
* @param functions
* the set of {@code <namespace, function>} pairs to filter on
* @param metadataHelper
* the metadata helper
* @return the set of fields found within the functions
*/
public static Set<FunctionFields> fetchFields(JexlNode query, Set<Pair<String,String>> functions, MetadataHelper metadataHelper) {
if (query != null) {
FetchFunctionFieldsVisitor visitor = new FetchFunctionFieldsVisitor(functions, metadataHelper);
query.jjtAccept(visitor, functions);
return visitor.getFunctionFields();
} else {
return Collections.emptySet();
}
}

private FetchFunctionFieldsVisitor(Set<Pair<String,String>> functions, MetadataHelper metadataHelper) {
if (functions == null || functions.isEmpty()) {
this.functions = Collections.emptySet();
} else {
this.functions = new HashSet<>();
functions.forEach((p) -> this.functions.add(Pair.of(p.getLeft(), p.getRight())));
}
this.metadataHelper = metadataHelper;
}

@Override
public Object visit(ASTFunctionNode node, Object data) {
FunctionJexlNodeVisitor visitor = new FunctionJexlNodeVisitor();
node.jjtAccept(visitor, null);

Pair<String,String> function = Pair.of(visitor.namespace(), visitor.name());
// If we are either not filtering out functions, or the function filters contains the functions, fetch the fields.
if (functions.isEmpty() || functions.contains(function)) {
JexlArgumentDescriptor desc = JexlFunctionArgumentDescriptorFactory.F.getArgumentDescriptor(node);
Set<String> fields = desc.fields(metadataHelper, null);
// Add the fields to the function.
if (!fields.isEmpty()) {
this.fields.putAll(function, fields);
}
}
return null;
}

// Returns the fields map as a set of FunctionFields.
private Set<FunctionFields> getFunctionFields() {
// Maintain insertion order.
Set<FunctionFields> functionFields = new LinkedHashSet<>();
for (Pair<String,String> function : fields.keySet()) {
functionFields.add(new FunctionFields(function.getLeft(), function.getRight(), fields.get(function)));
}
return functionFields;
}

public static class FunctionFields {
private final String namespace;
private final String function;
private final Set<String> fields;

public static FunctionFields of(String namespace, String function, String... fields) {
return new FunctionFields(namespace, function, Arrays.asList(fields));
}

private FunctionFields(String namespace, String function, Collection<String> fields) {
this.namespace = namespace;
this.function = function;
// Maintain insertion order.
this.fields = fields.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(new LinkedHashSet<>(fields));
}

public String getNamespace() {
return namespace;
}

public String getFunction() {
return function;
}

public Set<String> getFields() {
return fields;
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
FunctionFields that = (FunctionFields) object;
return Objects.equals(namespace, that.namespace) && Objects.equals(function, that.function) && Objects.equals(fields, that.fields);
}

@Override
public int hashCode() {
return Objects.hash(namespace, function, fields);
}

@Override
public String toString() {
return new StringJoiner(", ", FunctionFields.class.getSimpleName() + "[", "]").add("namespace='" + namespace + "'")
.add("function='" + function + "'").add("fields=" + fields).toString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
Expand Down Expand Up @@ -66,7 +67,8 @@ public FieldMissingFromSchemaVisitor(MetadataHelper helper, Set<String> datatype
@SuppressWarnings("unchecked")
public static Set<String> getNonExistentFields(MetadataHelper helper, ASTJexlScript script, Set<String> datatypes, Set<String> specialFields) {
FieldMissingFromSchemaVisitor visitor = new FieldMissingFromSchemaVisitor(helper, datatypes, specialFields);
return (Set<String>) script.jjtAccept(visitor, new HashSet<>());
// Maintain insertion order.
return (Set<String>) script.jjtAccept(visitor, new LinkedHashSet<>());
}

/**
Expand Down
Loading
Loading