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

Add Criteria-Based Filtering #894

Closed
wants to merge 57 commits into from
Closed
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
bbe78ea
feat: create sample code
otaviojava Nov 5, 2024
e48ca60
feat: create filter and criteria
otaviojava Nov 5, 2024
a5bdaa3
feat: include header to CriteriaRepository
otaviojava Nov 5, 2024
113d0c2
feat: rename core classes to Restriction and Operator
otaviojava Nov 6, 2024
592a100
feat: include logical operator
otaviojava Nov 6, 2024
4cb44d3
feat: create restriction proposal
otaviojava Nov 6, 2024
8b8898f
feat: create and update queries at RestrictionRepository
otaviojava Nov 6, 2024
8ceb3a5
style: inclulde header license
otaviojava Nov 6, 2024
ef4fd7b
feat: update structure based on the discussion on meeting today
otaviojava Nov 6, 2024
ee8b4fd
refact: convert restriction to interface
otaviojava Nov 6, 2024
076062f
feat: update the implementation using record metamodel
otaviojava Nov 6, 2024
7697efd
feat: move attribute to defult method
otaviojava Nov 6, 2024
4be722f
style: remove unsed imports
otaviojava Nov 6, 2024
08de315
feat: Use the CompositeRestriction
otaviojava Nov 6, 2024
391ab6f
feat: remove filterable attribute
otaviojava Nov 6, 2024
0a2a63a
feat: move restriction to metadmodel
otaviojava Nov 6, 2024
d4bc32c
feat: move ppattern to use internally
otaviojava Nov 6, 2024
596f49b
chore: remove ISNULL condition
otaviojava Nov 6, 2024
22b0f4a
Update api/src/main/java/jakarta/data/metamodel/TextAttribute.java
otaviojava Nov 6, 2024
34c00e5
feat: create range class
otaviojava Nov 6, 2024
785abd6
docs: update documentation on pattern
otaviojava Nov 6, 2024
d51a97e
feat: udpate using range
otaviojava Nov 6, 2024
0265f04
feat: remove unsed imports
otaviojava Nov 6, 2024
91f7e28
feat: update text attribute
otaviojava Nov 7, 2024
bd8b5b8
feat: update pattern
otaviojava Nov 7, 2024
e5bdf6e
feat: create restriction
otaviojava Nov 7, 2024
d6010ca
Update api/src/main/java/jakarta/data/metamodel/Pattern.java
otaviojava Nov 7, 2024
3c4369b
Update api/src/main/java/jakarta/data/metamodel/BasicRestriction.java
otaviojava Nov 7, 2024
d0837ad
Update api/src/main/java/jakarta/data/metamodel/BasicRestriction.java
otaviojava Nov 7, 2024
5f0d857
Update api/src/main/java/jakarta/data/metamodel/Pattern.java
otaviojava Nov 7, 2024
97e2dbe
Update api/src/main/java/jakarta/data/metamodel/Pattern.java
otaviojava Nov 7, 2024
d332f7e
Update api/src/main/java/jakarta/data/metamodel/Pattern.java
otaviojava Nov 7, 2024
1fff489
Update api/src/main/java/jakarta/data/metamodel/Pattern.java
otaviojava Nov 7, 2024
1b072f6
Update api/src/main/java/jakarta/data/metamodel/MultipleRestriction.java
otaviojava Nov 7, 2024
5d429a2
feat: reduce the scope with is annotation
otaviojava Nov 7, 2024
e37834d
Update api/src/main/java/jakarta/data/metamodel/Pattern.java
otaviojava Nov 7, 2024
11e048a
Update api/src/main/java/jakarta/data/metamodel/Pattern.java
otaviojava Nov 7, 2024
6b9d102
Update api/src/main/java/jakarta/data/metamodel/Pattern.java
otaviojava Nov 7, 2024
225a806
docs: fix pattern documentation
otaviojava Nov 7, 2024
e3a79b5
Update api/src/main/java/jakarta/data/metamodel/MultipleRestriction.java
otaviojava Nov 11, 2024
e3c8d87
feat: moved text attribute
otaviojava Nov 12, 2024
97586f0
Update api/src/main/java/jakarta/data/Pattern.java
otaviojava Nov 13, 2024
b5b61f0
feat: update pattern structure based on Gavin's point
otaviojava Nov 13, 2024
f0f551a
feat: create pattern structure bases on gavin suggestion
otaviojava Nov 13, 2024
da4cfdf
docs: update documentation in pattern
otaviojava Nov 13, 2024
a17d2ed
feat: update pattern name
otaviojava Nov 13, 2024
6d2916b
feat: update module info
otaviojava Nov 13, 2024
ca058c4
feat: update structure from PR 895
otaviojava Nov 13, 2024
13bd83d
feat: update to restrict
otaviojava Nov 13, 2024
d44817d
documentation: include restrict javadoc
otaviojava Nov 13, 2024
d973ec7
feat: create instances at TextAttribute using Restrict
otaviojava Nov 13, 2024
d8ada64
test: create scenario cases to pattern
otaviojava Nov 17, 2024
0897e47
test: create test to CompositeRestriction
otaviojava Nov 17, 2024
3c64b07
feat: remove unsed imports
otaviojava Nov 17, 2024
216877a
test: create basic restrictionr record
otaviojava Nov 17, 2024
5515485
test: create restrict test
otaviojava Nov 17, 2024
f0b8a5b
test: create test scenarios to Range
otaviojava Nov 17, 2024
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
79 changes: 79 additions & 0 deletions api/src/main/java/jakarta/data/Operator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package jakarta.data;


/**
* Enum representing common operators used to define query conditions in Jakarta Data queries.
* The {@code Operator} enum provides a set of operations to specify how values should be compared,
* matched, or restricted when filtering data, supporting flexible and expressive querying across
* different contexts.
*/
public enum Operator {

/**
* Matches records where the field value is exactly equal to the specified value.
* Typically used for exact matching on unique identifiers, names, or other exact-value fields.
*/
EQUAL,

/**
* Matches records where the field value is greater than the specified value.
* Often applied to numerical fields (e.g., dates, prices) for retrieving values above a given threshold.
*/
GREATER_THAN,

/**
* Matches records where the field value is greater than or equal to the specified value.
* Useful for inclusive range queries, where the specified boundary is included in the results.
*/
GREATER_THAN_EQUAL,

/**
* Matches records where the field value is contained within a specified collection of values.
* Commonly used to match against multiple possible values, such as category or status lists.
*/
IN,

/**
* Matches records where the field value is less than the specified value.
* Frequently used in numerical comparisons to retrieve values below a certain threshold.
*/
LESS_THAN,

/**
* Matches records where the field value is less than or equal to the specified value.
* Suitable for inclusive range queries that include the specified boundary in the results.
*/
LESS_THAN_EQUAL,

/**
* Matches records where the field value conforms to a specified pattern, often with wildcards.
* Commonly applied to string fields for partial matches (e.g., names containing a substring).
*/
LIKE,

/**
* Matches records where the field value falls within a specified range.
* Typically used for range-based comparisons, such as finding dates between a start and end date.
*/
BETWEEN
}



8 changes: 4 additions & 4 deletions api/src/main/java/jakarta/data/Order.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
*/
package jakarta.data;

import java.util.Iterator;
import java.util.List;

import jakarta.data.metamodel.StaticMetamodel;
import jakarta.data.repository.OrderBy;

import java.util.Iterator;
import java.util.List;

/**
* <p>Requests sorting on various entity attributes.</p>
*
Expand Down Expand Up @@ -169,4 +169,4 @@ public Iterator<Sort<? super T>> iterator() {
public String toString() {
return sorts.toString();
}
}
}
96 changes: 96 additions & 0 deletions api/src/main/java/jakarta/data/Pattern.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package jakarta.data;

import jakarta.data.metamodel.Restriction;


/**
* Represents a pattern-based restriction for matching operations, encapsulating different
* options such as prefix, suffix, and substring matching. This implementation
* allows flexibility in creating pattern-based conditions directly as {@link Restriction} instances.
*
* <p>Example usage with metadata attributes:</p>
* <pre>
* Restriction<Book> prefixIgnoreCase = _Book.title.endsWith("Guide");
otaviojava marked this conversation as resolved.
Show resolved Hide resolved
* </pre>
*
*/
public record Pattern(String value, boolean ignoreCase) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not have ignoreCase as a field of Pattern. I understand logically it could make sense, but it is already a field of Restriction, and it is confusing to have it in both places where users will wonder which takes precedence,

employees.search(
    Restriction.like(new Pattern(prefixPattern, ignoreCase = false)).ignoreCase());

It would also be confusing in places like this:

employees.findByLastNameIgnoreCaseLike(new Pattern(prefixPattern, ignoreCase = false));

Omit it from Pattern and none of the above will even be possible.

Also, value is confusing. Specifically, this is a pattern for LIKE. If someone does,

String prefix = "Jakarta";
Pattern jakartaPrefixPattern = new Pattern(prefix

it won't actually behave as a prefix. We are going to need to make this very clear so it isn't misused (any maybe we don't even want to use a record with a public constructor)

I'm not sure what best to call it. Here is something I thought of quickly,

Suggested change
public record Pattern(String value, boolean ignoreCase) {
public record Pattern(String like) {

Copy link
Contributor

@gavinking gavinking Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not have ignoreCase as a field of Pattern.

I don't agree with this. One of the main nice things about Pattern is that it encapsulates case-sensitivity.

By original proposal was:

public record Pattern(String pattern, boolean caseSensitive) {

    public Pattern ignoringCase() {
        return new Pattern(pattern, false);
    }

    public static Pattern is(String literal) {
        return new Pattern(escape(literal), true);
    }

    public static Pattern matches(String pattern) {
        return new Pattern(pattern, true);
    }

    public static Pattern matches(String pattern,
                                  char characterWildcard, char stringWildcard) {
        final String standardized =
                escape(pattern)
                        .replace(characterWildcard, '_')
                        .replace(stringWildcard, '%');
        return new Pattern(standardized, true);
    }

    public static Pattern startsWith(String prefix) {
        return new Pattern(escape(prefix) + '%', true);
    }

    public static Pattern endsWith(String suffix) {
        return new Pattern('%' + escape(suffix), true);
    }

    public static Pattern contains(String substring) {
        return new Pattern('%' + escape(substring) + '%', true);
    }

    private static String escape(String literal) {
        return literal.replace("_", "\\_").replace("%", "\\%");
    }
}

Where, the idea was that users would not usually call the constructor to obtain a Pattern.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Irrespective of the discussion on ignoringCase, the automatic escaping that you proposed above seems very useful, and helps users avoid unintentional wildcards in their prefix/suffix/substring, which can be literals now instead of patterns. I like that better than what is under this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I agreed.
I moved this class to our PR.


/**
* Creates a pattern for a {@link Operator#LIKE LIKE} match on the specified value.
* This method sets the field to `null`, allowing it to be applied
* later to a specific attribute.
*
* @return a Pattern instance for a pattern match.
*/
public static Pattern like(String pattern) {
return new Pattern(pattern, false);
otaviojava marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Creates a pattern for a `LIKE` match where values start with the specified prefix.
* The `field` is set to `null` initially, allowing it to be assigned to an attribute later.
*
* @param value the prefix to match at the beginning of the field's value.
* @return a Pattern instance for a prefix match.
*/
public static Pattern startsWith(String value) {
return new Pattern( value + "%", false);
otaviojava marked this conversation as resolved.
Show resolved Hide resolved
otaviojava marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Creates a pattern for a `LIKE` match where values contain the specified substring.
* This method initializes the field to `null`, allowing the pattern to be applied to
* a specific attribute later.
*
* @param value the substring to match within the field's value.
* @return a Pattern instance for a substring match.
*/
public static Pattern contains(String value) {
return new Pattern("%" + value + "%", false);
otaviojava marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Creates a pattern for a `LIKE` match where values end with the specified suffix.
* The field is set to `null`, allowing assignment to a specific attribute later.
*
*
* @param value the suffix to match at the end of the field's value.
* @return a Pattern instance for a suffix match.
*/
public static Pattern endsWith(String value) {
return new Pattern(value + "%", false);
otaviojava marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Returns a new {@code Pattern} instance with case-insensitive matching.
* This method allows you to specify that the pattern should ignore case when matching.
*
* <pre>
* // Case-insensitive prefix match
* Restriction<Book> titlePattern = Pattern.startsWith("Hibernate").ignoringCase();
* </pre>
*
* @return a new Pattern instance with `ignoreCase` set to `true`.
*/
public Pattern ignoringCase() {
return new Pattern(value, true);
}
otaviojava marked this conversation as resolved.
Show resolved Hide resolved
}
20 changes: 20 additions & 0 deletions api/src/main/java/jakarta/data/metamodel/Attribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package jakarta.data.metamodel;

import jakarta.data.Operator;
import jakarta.data.Sort;

/**
Expand All @@ -32,4 +33,23 @@ public interface Attribute<T> {
* @return the entity attribute name.
*/
String name();

/**
* Creates an equality restriction for the attribute.
*
* @param value the value to match exactly.
* @return a Restriction representing an equality condition.
*/
default Restriction<T> equal(Object value) {
return new SimpleRestriction<>(name(), Operator.EQUAL, value);
}

/**
* Creates a restriction for checking if the attribute is null.
*
* @return a Restriction representing the condition where the attribute is null.
*/
default Restriction<T> isNull(){
return new SimpleRestriction<>(name(), Operator.EQUAL, null);
}
}
55 changes: 55 additions & 0 deletions api/src/main/java/jakarta/data/metamodel/BasicRestriction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package jakarta.data.metamodel;

import jakarta.data.Operator;

/**
* A basic restriction applies to a single field, representing conditions such as equality,
* comparisons, range checks, and pattern matches.
*
* <p>The {@code BasicRestriction} interface provides methods for defining simple, singular restrictions
* based on a specific field, an operator, and an optional comparison value. This interface supports
* common operators (e.g., EQUAL, GREATER_THAN) and serves as a foundation for filtering
* logic on individual fields.</p>
*
* @param <T> the type of the entity on which the restriction is applied.
*/
public interface BasicRestriction<T> extends Restriction<T> {

/**
* The name of the field on which this restriction is applied.
*
* @return the field name as a String.
*/
String field();

/**
* The operator defining the type of comparison or condition for this restriction.
*
* @return the operator representing the restriction type (e.g., EQUAL, LIKE, BETWEEN).
*/
Operator operator();

/**
* The value used for comparison in this restriction, if applicable.
*
* @return the comparison value, or {@code null} if the restriction does not use a value (e.g., IS_NULL).
*/
Object value();
}
72 changes: 72 additions & 0 deletions api/src/main/java/jakarta/data/metamodel/CompositeRestriction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package jakarta.data.metamodel;

import java.util.Iterator;
import java.util.List;

/**
* A composite restriction representing a collection of individual {@link Restriction}
* and {@link Restrict} instances, combined under a single logical operation.
*
* <p>This record allows multiple restrictions to be treated as a single entity, making
* it easy to pass complex conditions to repository methods.</p>
*
* @param <T> the entity type that the restrictions apply to.
*/
public record CompositeRestriction<T>(Restrict type, List<Restriction<? extends T>> restrictions) implements Iterable<Restriction<? extends T>>, MultipleRestriction<T> {

/**
* Constructs a composite restriction with the specified operator and list of restrictions.
*
* @param restrictions the list of restrictions to combine.
*/
public CompositeRestriction {
restrictions = List.copyOf(restrictions); // Ensure immutability of the list
}

@Override
public Iterator<Restriction<? extends T>> iterator() {
return restrictions.iterator();
}

/**
* Creates a composite restriction where all specified restrictions must be true (AND logic).
*
* @param restrictions the individual restrictions to combine.
* @param <T> the entity type that the restrictions apply to.
* @return a CompositeRestriction representing the AND combination of the provided restrictions.
*/
@SafeVarargs
public static <T> CompositeRestriction<T> all(Restriction<T>... restrictions) {
return new CompositeRestriction<>(Restrict.ALL, List.of(restrictions));
}

/**
* Creates a composite restriction where any of the specified restrictions may be true (OR logic).
*
* @param restrictions the individual restrictions to combine.
* @param <T> the entity type that the restrictions apply to.
* @return a CompositeRestriction representing the OR combination of the provided restrictions.
*/
@SafeVarargs
public static <T> CompositeRestriction<T> any(Restriction<T>... restrictions) {
return new CompositeRestriction<>(Restrict.ANY, List.of(restrictions));
}

}
Loading