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

dynamic query restrictions #895

Merged
merged 36 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d41c721
Issue #829 dynamic query restrictions
njr-11 Nov 8, 2024
ef9e462
Address most of the review comments from Gavin
njr-11 Nov 9, 2024
8ba74d5
prevent ignoreCase from being available to non-text restrictions
njr-11 Nov 9, 2024
9a72101
Move enum out of Restrict
njr-11 Nov 9, 2024
2b8a88c
Switch from pattern to literal in methods such as startsWith
njr-11 Nov 13, 2024
3867d24
add wildcards
njr-11 Nov 19, 2024
35dc014
Update api/src/main/java/jakarta/data/Restriction.java
njr-11 Nov 20, 2024
1afe5f7
review comment to move interfaces to top level instead of inner classes
njr-11 Nov 20, 2024
2b2ab75
Update api/src/main/java/jakarta/data/Restrict.java
njr-11 Nov 21, 2024
18c00b3
Review comments on Comparable, notEqualTo, and Operator
njr-11 Nov 21, 2024
2f578c5
test: create initial structure to restrictTest
otaviojava Nov 21, 2024
13640fd
test: create test scenarios to restrict
otaviojava Nov 21, 2024
d2f0fe0
feat: update the method to became default package
otaviojava Nov 21, 2024
6cee833
docs: update documentation to Restrict
otaviojava Nov 21, 2024
c047e0f
test: create structure to basicrestriction record
otaviojava Nov 21, 2024
e2f899e
test: create BasicRestrictionRecord scenarions
otaviojava Nov 21, 2024
af29f92
test: create structure to composite restrictionrecord
otaviojava Nov 21, 2024
8a93a8a
test: create scenarios to CompositeRestrictionREcord
otaviojava Nov 21, 2024
c6f835e
test: create scenarios to TextRestrictionRecord
otaviojava Nov 21, 2024
711497c
Merge pull request #1 from soujava/829-dynamic-query-restrictions
njr-11 Nov 21, 2024
a8707ca
Enforce that TextRestriction.value() is a String, and reject empty co…
njr-11 Nov 21, 2024
8bef07a
test: create initial structure to attrivute test
otaviojava Nov 22, 2024
305a552
test: create test to attribute scenarios
otaviojava Nov 22, 2024
4fb6854
test: create test structure to Sortableattribute
otaviojava Nov 22, 2024
a93f6e9
style: fix imports at attributetest
otaviojava Nov 22, 2024
7784d3b
test: create SortableAttribute test
otaviojava Nov 22, 2024
8e9720e
test: include test to sortable attribute test
otaviojava Nov 22, 2024
6804be2
test: create structure to textattribute
otaviojava Nov 22, 2024
a87a71f
test: create scenarios to TextAttribute
otaviojava Nov 22, 2024
8d01623
Merge pull request #2 from soujava/829-dynamic-query-restrictions
njr-11 Nov 26, 2024
a780900
feat: create field validation
otaviojava Nov 27, 2024
ac428fa
test: create test validation
otaviojava Nov 27, 2024
6bff08d
test: create validation to field on basic restriction
otaviojava Nov 27, 2024
822de0f
test: create textrestriction record
otaviojava Nov 27, 2024
014fd07
fix: update import at TextRestriction
otaviojava Nov 27, 2024
1e70a1a
Merge pull request #3 from soujava/829-dynamic-query-restrictions
njr-11 Nov 27, 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
19 changes: 6 additions & 13 deletions api/src/main/java/jakarta/data/BasicRestriction.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-License-Identifier: Apache-2.0
*/
package jakarta.data;

// Internal implementation class.
// The proper way for users to obtain instances is via
// the static metamodel or Restrict.* methods
public interface BasicRestriction<T> extends Restriction<T> {
Operator comparison();

record BasicRestriction<T>(
String field,
boolean isNegated,
Operator comparison,
Object value) implements Restriction.Basic<T> {
String field();

BasicRestriction(String field, Operator comparison, Object value) {
this(field, false, comparison, value);
}
}
Object value();
}
33 changes: 33 additions & 0 deletions api/src/main/java/jakarta/data/BasicRestrictionRecord.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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;

// Internal implementation class.
// The proper way for users to obtain instances is via
// the static metamodel or Restrict.* methods

record BasicRestrictionRecord<T>(
String field,
boolean isNegated,
Operator comparison,
Object value) implements BasicRestriction<T> {

BasicRestrictionRecord(String field, Operator comparison, Object value) {
this(field, false, comparison, value);
}
}
21 changes: 8 additions & 13 deletions api/src/main/java/jakarta/data/CompositeRestriction.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-License-Identifier: Apache-2.0
*/
package jakarta.data;

import java.util.List;

// Internal implementation class.
// The proper way for users to obtain instances is via
// the Restrict.any(...) or Restrict.all(...) methods
public interface CompositeRestriction<T> extends Restriction<T> {
List<Restriction<T>> restrictions();

record CompositeRestriction<T>(
Composite.Type type,
List<Restriction<T>> restrictions,
boolean isNegated) implements Restriction.Composite<T> {
Type type();

CompositeRestriction(
Composite.Type type,
List<Restriction<T>> restrictions) {
this(type, restrictions, false);
enum Type {
ALL,
ANY
}
}
}
34 changes: 34 additions & 0 deletions api/src/main/java/jakarta/data/CompositeRestrictionRecord.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 java.util.List;

// Internal implementation class.
// The proper way for users to obtain instances is via
// the Restrict.any(...) or Restrict.all(...) methods

record CompositeRestrictionRecord<T>(
Type type,
List<Restriction<T>> restrictions,
boolean isNegated) implements CompositeRestriction<T> {

CompositeRestrictionRecord(Type type, List<Restriction<T>> restrictions) {
this(type, restrictions, false);
}
}
89 changes: 45 additions & 44 deletions api/src/main/java/jakarta/data/Restrict.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.util.List;
import java.util.Set;

import jakarta.data.Restriction.Composite;
import jakarta.data.Restriction.Operator;

// TODO document
Expand All @@ -44,12 +43,14 @@ private Restrict() {

@SafeVarargs
public static <T> Restriction<T> all(Restriction<T>... restrictions) {
return new CompositeRestriction<>(Composite.Type.ALL, List.of(restrictions));
return new CompositeRestrictionRecord<>(CompositeRestriction.Type.ALL,
List.of(restrictions));
}

@SafeVarargs
public static <T> Restriction<T> any(Restriction<T>... restrictions) {
return new CompositeRestriction<>(Composite.Type.ANY, List.of(restrictions));
return new CompositeRestrictionRecord<>(CompositeRestriction.Type.ANY,
List.of(restrictions));
}

public static <T> Restriction<T> between(Comparable<Object> min,
Expand All @@ -62,119 +63,119 @@ public static <T> Restriction<T> between(Comparable<Object> min,
// TODO Need to think more about how to best cover negation of multiple
// and then make negation of Single consistent with it

public static <T> Restriction.Text<T> contains(String substring, String field) {
public static <T> TextRestriction<T> contains(String substring, String field) {
String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, true, substring, true);
return new TextRestriction<>(field, Operator.LIKE, ESCAPED, pattern);
return new TextRestrictionRecord<>(field, Operator.LIKE, ESCAPED, pattern);
}

public static <T> Restriction.Text<T> endsWith(String suffix, String field) {
public static <T> TextRestriction<T> endsWith(String suffix, String field) {
String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, true, suffix, false);
return new TextRestriction<>(field, Operator.LIKE, ESCAPED, pattern);
return new TextRestrictionRecord<>(field, Operator.LIKE, ESCAPED, pattern);
}

public static <T> Restriction<T> equalTo(Object value, String field) {
return new BasicRestriction<>(field, Operator.EQUAL, value);
return new BasicRestrictionRecord<>(field, Operator.EQUAL, value);
}

public static <T> Restriction.Text<T> equalTo(String value, String field) {
return new TextRestriction<>(field, Operator.EQUAL, value);
public static <T> TextRestriction<T> equalTo(String value, String field) {
return new TextRestrictionRecord<>(field, Operator.EQUAL, value);
}

public static <T> Restriction<T> greaterThan(Comparable<Object> value, String field) {
return new BasicRestriction<>(field, Operator.GREATER_THAN, value);
return new BasicRestrictionRecord<>(field, Operator.GREATER_THAN, value);
}

public static <T> Restriction.Text<T> greaterThan(String value, String field) {
return new TextRestriction<>(field, Operator.GREATER_THAN, value);
public static <T> TextRestriction<T> greaterThan(String value, String field) {
return new TextRestrictionRecord<>(field, Operator.GREATER_THAN, value);
}

public static <T> Restriction<T> greaterThanEqual(Comparable<Object> value, String field) {
njr-11 marked this conversation as resolved.
Show resolved Hide resolved
return new BasicRestriction<>(field, Operator.GREATER_THAN_EQUAL, value);
return new BasicRestrictionRecord<>(field, Operator.GREATER_THAN_EQUAL, value);
}

public static <T> Restriction.Text<T> greaterThanEqual(String value, String field) {
return new TextRestriction<>(field, Operator.GREATER_THAN_EQUAL, value);
public static <T> TextRestriction<T> greaterThanEqual(String value, String field) {
return new TextRestrictionRecord<>(field, Operator.GREATER_THAN_EQUAL, value);
}

public static <T> Restriction<T> in(Set<Object> values, String field) {
return new BasicRestriction<>(field, Operator.IN, values);
return new BasicRestrictionRecord<>(field, Operator.IN, values);
}

public static <T> Restriction<T> lessThan(Comparable<Object> value, String field) {
return new BasicRestriction<>(field, Operator.LESS_THAN, value);
return new BasicRestrictionRecord<>(field, Operator.LESS_THAN, value);
}

public static <T> Restriction.Text<T> lessThan(String value, String field) {
return new TextRestriction<>(field, Operator.LESS_THAN, value);
public static <T> TextRestriction<T> lessThan(String value, String field) {
return new TextRestrictionRecord<>(field, Operator.LESS_THAN, value);
}

public static <T> Restriction<T> lessThanEqual(Comparable<Object> value, String field) {
return new BasicRestriction<>(field, Operator.LESS_THAN_EQUAL, value);
return new BasicRestrictionRecord<>(field, Operator.LESS_THAN_EQUAL, value);
}

public static <T> Restriction.Text<T> lessThanEqual(String value, String field) {
return new TextRestriction<>(field, Operator.LESS_THAN_EQUAL, value);
public static <T> TextRestriction<T> lessThanEqual(String value, String field) {
return new TextRestrictionRecord<>(field, Operator.LESS_THAN_EQUAL, value);
}

// TODO this would be possible if Pattern is added, but is it even useful?
//public static <T> Restriction.Text<T> like(Pattern pattern, String field) {
//public static <T> TextRestriction<T> like(Pattern pattern, String field) {
// return new TextRestriction<>(field, Operator.LIKE, ESCAPED, pattern);
//}

public static <T> Restriction.Text<T> like(String pattern, String field) {
return new TextRestriction<>(field, Operator.LIKE, pattern);
public static <T> TextRestriction<T> like(String pattern, String field) {
return new TextRestrictionRecord<>(field, Operator.LIKE, pattern);
}

public static <T> Restriction.Text<T> like(String pattern,
public static <T> TextRestriction<T> like(String pattern,
char charWildcard,
char stringWildcard,
String field) {
String p = toLikeEscaped(charWildcard, stringWildcard, false, pattern, false);
return new TextRestriction<>(field, Operator.LIKE, ESCAPED, p);
return new TextRestrictionRecord<>(field, Operator.LIKE, ESCAPED, p);
}

public static <T> Restriction<T> not(Object value, String field) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It is not clear that not means, notEquals´. Please either, use neorNotEquals`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I can update that. Given that we have a equalTo method, the equivalent here will be notEqualTo

return new BasicRestriction<>(field, NOT, Operator.EQUAL, value);
return new BasicRestrictionRecord<>(field, NOT, Operator.EQUAL, value);
}

public static <T> Restriction.Text<T> not(String value, String field) {
return new TextRestriction<>(field, NOT, Operator.EQUAL, value);
public static <T> TextRestriction<T> not(String value, String field) {
return new TextRestrictionRecord<>(field, NOT, Operator.EQUAL, value);
}

public static <T> Restriction.Text<T> notContains(String substring, String field) {
public static <T> TextRestriction<T> notContains(String substring, String field) {
Copy link
Contributor

@otaviojava otaviojava Nov 21, 2024

Choose a reason for hiding this comment

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

Can we remove all of those no method factory sequences?

It would be great if we have only two options: by the method instead negateand a single method factory not.

It is pretty similar to the Java Predicate: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/function/Predicate.html

TextRestriction<Book> = TextRestriction.not(TextRestriction.contains(...));
TextRestriction<Book> = TextRestriction.contains(...).negate();

Copy link
Contributor

Choose a reason for hiding this comment

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

@otaviojava JDQL has operators like not between, not like, etc, so it makes sense to me for such things to be reflected here. And it's significantly less verbose.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with Gavin on this.

Being able to write code such as,

products.search(_Product.description.notContains("refurbished"));

is much more straightforward and readable than

products.search(_Product.description.contains("refurbished").negated());

or

products.search(Restrict.not(_Product.description.contains("refurbished")));

String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, true, substring, true);
return new TextRestriction<>(field, NOT, Operator.LIKE, ESCAPED, pattern);
return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, ESCAPED, pattern);
}

public static <T> Restriction.Text<T> notEndsWith(String suffix, String field) {
public static <T> TextRestriction<T> notEndsWith(String suffix, String field) {
String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, true, suffix, false);
return new TextRestriction<>(field, NOT, Operator.LIKE, ESCAPED, pattern);
return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, ESCAPED, pattern);
}

public static <T> Restriction<T> notIn(Set<Object> values, String field) {
return new BasicRestriction<>(field, NOT, Operator.IN, values);
return new BasicRestrictionRecord<>(field, NOT, Operator.IN, values);
}

public static <T> Restriction.Text<T> notLike(String pattern, String field) {
return new TextRestriction<>(field, NOT, Operator.LIKE, pattern);
public static <T> TextRestriction<T> notLike(String pattern, String field) {
return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, pattern);
}

public static <T> Restriction.Text<T> notLike(String pattern,
public static <T> TextRestriction<T> notLike(String pattern,
char charWildcard,
char stringWildcard,
String field) {
String p = toLikeEscaped(charWildcard, stringWildcard, false, pattern, false);
return new TextRestriction<>(field, NOT, Operator.LIKE, ESCAPED, p);
return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, ESCAPED, p);
}

public static <T> Restriction.Text<T> notStartsWith(String prefix, String field) {
public static <T> TextRestriction<T> notStartsWith(String prefix, String field) {
String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, false, prefix, true);
return new TextRestriction<>(field, NOT, Operator.LIKE, ESCAPED, pattern);
return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, ESCAPED, pattern);
}

public static <T> Restriction.Text<T> startsWith(String prefix, String field) {
public static <T> TextRestriction<T> startsWith(String prefix, String field) {
String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, false, prefix, true);
return new TextRestriction<>(field, Operator.LIKE, ESCAPED, pattern);
return new TextRestrictionRecord<>(field, Operator.LIKE, ESCAPED, pattern);
}

/**
Expand Down
29 changes: 0 additions & 29 deletions api/src/main/java/jakarta/data/Restriction.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,9 @@
*/
package jakarta.data;

import java.util.List;

public interface Restriction<T> {
boolean isNegated();

interface Basic<T> extends Restriction<T> {
Operator comparison();

String field();

Object value();
}

interface Composite<T> extends Restriction<T> {
List<Restriction<T>> restrictions();

Type type();

enum Type {
ALL,
ANY
}
}

interface Text<T> extends Basic<T> {
Restriction<T> ignoreCase();

boolean isCaseSensitive();

boolean isEscaped();
}

enum Operator {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove this one from the inner class; it is good, mainly for the Javadoc.

Copy link
Contributor Author

@njr-11 njr-11 Nov 21, 2024

Choose a reason for hiding this comment

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

I suppose I could move it to a top level class, although note that it currently is not something that applications use. Its current purpose is for Jakarta Data providers to know which type of restriction it is, so I had intentionally made in an inner class to keep it more out of the way of application developers. If you are looking ahead to the possibility of reusing it for an @Is annotation, then it would become something that is directly used by applications.

Copy link
Contributor

Choose a reason for hiding this comment

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

@is annotation

That is what I do have on my mind.

EQUAL,
GREATER_THAN,
Expand Down
Loading