Repository navigation
Add inverse check to NumericValueRule for non-numeric values on NumberType fields #3993
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
base: integration
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| package datawave.query.jexl.visitors; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.LinkedHashSet; | ||
| import java.util.Set; | ||
|
|
||
| import org.apache.commons.jexl3.parser.ASTEQNode; | ||
| import org.apache.commons.jexl3.parser.ASTGENode; | ||
| import org.apache.commons.jexl3.parser.ASTGTNode; | ||
| import org.apache.commons.jexl3.parser.ASTJexlScript; | ||
| 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.JexlNode; | ||
| import org.apache.commons.lang3.math.NumberUtils; | ||
|
|
||
| import datawave.query.jexl.JexlASTHelper; | ||
|
|
||
| /** | ||
| * Visitor that collects fields whose comparison literal is a string that cannot be parsed as a number. | ||
| * <p> | ||
| * All simple comparison operators are inspected (equality, inequality, and the four range operators). | ||
| * Null and numeric literals are ignored. This is the inverse of {@link FieldsWithNumericValuesVisitor}; | ||
| * see also {@link FieldsWithNumericRangeVisitor} for the range only numeric case. | ||
|
Check failure on line 24 in warehouse/query-core/src/main/java/datawave/query/jexl/visitors/FieldsWithNonNumericValuesVisitor.java
|
||
| */ | ||
| public class FieldsWithNonNumericValuesVisitor extends ShortCircuitBaseVisitor { | ||
|
|
||
| /** | ||
| * Fetch all fields that are compared against a non-numeric string literal. | ||
| * | ||
| * @param query | ||
| * The parsed JEXL query script | ||
| * @return ordered set of field names | ||
| */ | ||
| @SuppressWarnings("unchecked") | ||
| public static Set<String> getFields(ASTJexlScript query) { | ||
| if (query == null) { | ||
| return Collections.emptySet(); | ||
| } else { | ||
| FieldsWithNonNumericValuesVisitor visitor = new FieldsWithNonNumericValuesVisitor(); | ||
| return (Set<String>) query.jjtAccept(visitor, new LinkedHashSet<String>()); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public Object visit(ASTEQNode node, Object data) { | ||
| checkSingleField(node, data); | ||
| return data; | ||
| } | ||
|
|
||
| @Override | ||
| public Object visit(ASTNENode node, Object data) { | ||
| checkSingleField(node, data); | ||
| return data; | ||
| } | ||
|
|
||
| @Override | ||
| public Object visit(ASTLTNode node, Object data) { | ||
| checkSingleField(node, data); | ||
| return data; | ||
| } | ||
|
|
||
| @Override | ||
| public Object visit(ASTGTNode node, Object data) { | ||
| checkSingleField(node, data); | ||
| return data; | ||
| } | ||
|
|
||
| @Override | ||
| public Object visit(ASTLENode node, Object data) { | ||
| checkSingleField(node, data); | ||
| return data; | ||
| } | ||
|
|
||
| @Override | ||
| public Object visit(ASTGENode node, Object data) { | ||
| checkSingleField(node, data); | ||
| return data; | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| private void checkSingleField(JexlNode node, Object data) { | ||
| String field = JexlASTHelper.getIdentifier(node); | ||
| if (field != null) { | ||
| Object literal = JexlASTHelper.getLiteralValueSafely(node); | ||
| if (literal instanceof String && !NumberUtils.isCreatable((String) literal)) { | ||
| ((Set<String>) data).add(field); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package datawave.query.jexl.visitors; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
|
||
| import java.util.LinkedHashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| import org.apache.commons.jexl3.parser.ASTJexlScript; | ||
| import org.apache.commons.jexl3.parser.ParseException; | ||
| import org.junit.jupiter.api.AfterEach; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.ValueSource; | ||
|
|
||
| import datawave.query.jexl.JexlASTHelper; | ||
|
|
||
| class FieldsWithNonNumericValuesVisitorTest { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice test coverage. |
||
|
|
||
| private String query; | ||
| private final Set<String> expectedFields = new LinkedHashSet<>(); | ||
|
|
||
| @AfterEach | ||
| void tearDown() { | ||
| query = null; | ||
| expectedFields.clear(); | ||
| } | ||
|
|
||
| /** | ||
| * Test the various field operators with non-numeric string values. | ||
| * | ||
| * @param operator | ||
| * The operator | ||
| */ | ||
| @ParameterizedTest | ||
| @ValueSource(strings = {"==", "!=", "<", ">", "<=", ">="}) | ||
| void testOperatorsWithTextValue(String operator) throws ParseException { | ||
| givenQuery("FOO " + operator + " 'abc'"); | ||
| expectFields("FOO"); | ||
| assertResult(); | ||
| } | ||
|
|
||
| /** | ||
| * Test the various field operators with numeric literal values. | ||
| * | ||
| * @param operator | ||
| * The operator | ||
| */ | ||
| @ParameterizedTest | ||
| @ValueSource(strings = {"==", "!=", "<", ">", "<=", ">="}) | ||
| void testOperatorsWithNumericLiteral(String operator) throws ParseException { | ||
| givenQuery("FOO " + operator + " 1"); | ||
|
|
||
| // Do not expect any fields. | ||
| assertResult(); | ||
| } | ||
|
|
||
| /** | ||
| * Test string literals that are valid numbers. | ||
| */ | ||
| @ParameterizedTest | ||
| @ValueSource(strings = {"'1'", "'2.0'", "'-5'", "'1e3'"}) | ||
| void testFieldWithStringThatIsNumeric(String literal) throws ParseException { | ||
| givenQuery("FOO == " + literal); | ||
|
|
||
| // Do not expect any fields. | ||
| assertResult(); | ||
| } | ||
|
|
||
| /** | ||
| * Test null literals are ignored. | ||
| */ | ||
| @Test | ||
| void testNullLiteralIgnored() throws ParseException { | ||
| givenQuery("FOO == null"); | ||
|
|
||
| // Do not expect any fields. | ||
| assertResult(); | ||
| } | ||
|
|
||
| /** | ||
| * Test a mix of numeric and non-numeric literals across several fields. | ||
| */ | ||
| @Test | ||
| void testMultipleFieldsWithMixedValues() throws ParseException { | ||
| givenQuery("FOO == 'abc' && BAR != '>60' || HAT > 3 || BAT < '5' || HEN == 'xyz'"); | ||
| expectFields("FOO", "BAR", "HEN"); | ||
| assertResult(); | ||
| } | ||
|
|
||
| private void givenQuery(String query) { | ||
| this.query = query; | ||
| } | ||
|
|
||
| private void expectFields(String... fields) { | ||
| this.expectedFields.addAll(List.of(fields)); | ||
| } | ||
|
|
||
| private void assertResult() throws ParseException { | ||
| ASTJexlScript script =JexlASTHelper.parseJexlQuery(query); | ||
| Set<String> actual = FieldsWithNonNumericValuesVisitor.getFields(script); | ||
| assertEquals(expectedFields, actual); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.