below(T upperBound) {
+ return Range.below(name(), upperBound);
+ }
+
}
diff --git a/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java b/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
index b0a4f47a5..1e644833f 100644
--- a/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
+++ b/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
@@ -17,14 +17,14 @@
*/
package jakarta.data.metamodel;
+import jakarta.data.Sort;
+
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import jakarta.data.Sort;
-
/**
* Annotates a class which serves as a static metamodel for an entity, enabling
* type-safe access to entity attribute names and related objects such as instances
diff --git a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
index c00390f09..351ab5ccd 100644
--- a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
@@ -17,10 +17,16 @@
*/
package jakarta.data.metamodel;
+import jakarta.data.Operator;
+import jakarta.data.Pattern;
+import jakarta.data.Restrict;
+import jakarta.data.Restriction;
import jakarta.data.Sort;
+import jakarta.data.metamodel.impl.BasicRestrictionRecord;
+
/**
- * Represents an textual entity attribute in the {@link StaticMetamodel}.
+ * Represents a textual entity attribute in the {@link StaticMetamodel}.
*
* @param entity class of the static metamodel.
*/
@@ -40,4 +46,55 @@ public interface TextAttribute extends SortableAttribute {
*/
Sort descIgnoreCase();
+ /**
+ * Creates a `LIKE` restriction for a match on the specified text.
+ *
+ * @param text the text to match.
+ * @return a Restriction representing a `LIKE` condition.
+ */
+ default Restriction like(String text) {
+ return new BasicRestrictionRecord<>(name(), Operator.LIKE, text);
+ }
+
+ /**
+ * Creates a `LIKE` restriction for a match on the specified pattern.
+ *
+ * @param pattern the `Pattern` instance to match.
+ * @return a Restriction representing a `LIKE` condition.
+ */
+ default Restriction like(Pattern pattern) {
+ return new BasicRestrictionRecord<>(name(), Operator.LIKE, pattern.value());
+ }
+
+ /**
+ * Creates a `LIKE` restriction for values that start with the specified text.
+ *
+ * @param text the text prefix to match.
+ * @return a Restriction representing a prefix `LIKE` condition.
+ */
+ default Restriction startsWith(String text) {
+ return Restrict.startsWith(text, name());
+ }
+
+ /**
+ * Creates a `LIKE` restriction for values that contain the specified substring.
+ *
+ * @param text the substring to match.
+ * @return a Restriction representing a substring `LIKE` condition.
+ */
+ default Restriction contains(String text) {
+ return Restrict.contains(text, name());
+ }
+
+
+ /**
+ * Creates a `LIKE` restriction for values that end with the specified text.
+ *
+ * @param text the text suffix to match.
+ * @return a Restriction representing a suffix `LIKE` condition.
+ */
+ default Restriction endsWith(String text) {
+ return Restrict.endsWith(text, name());
+ }
+
}
diff --git a/api/src/main/java/jakarta/data/metamodel/impl/AttributeRecord.java b/api/src/main/java/jakarta/data/metamodel/impl/AttributeRecord.java
index 31d329af7..48fc78bde 100644
--- a/api/src/main/java/jakarta/data/metamodel/impl/AttributeRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/impl/AttributeRecord.java
@@ -28,5 +28,6 @@
*/
public record AttributeRecord(String name)
implements Attribute {
+
}
diff --git a/api/src/main/java/jakarta/data/metamodel/impl/BasicRestrictionRecord.java b/api/src/main/java/jakarta/data/metamodel/impl/BasicRestrictionRecord.java
new file mode 100644
index 000000000..810d80343
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/impl/BasicRestrictionRecord.java
@@ -0,0 +1,25 @@
+/*
+ * 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.impl;
+
+import jakarta.data.Operator;
+import jakarta.data.BasicRestriction;
+
+public record BasicRestrictionRecord(String field, Operator operator, Object value) implements BasicRestriction {
+
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/impl/SortableAttributeRecord.java b/api/src/main/java/jakarta/data/metamodel/impl/SortableAttributeRecord.java
index 73f1df791..ed8c15211 100644
--- a/api/src/main/java/jakarta/data/metamodel/impl/SortableAttributeRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/impl/SortableAttributeRecord.java
@@ -37,5 +37,6 @@ public Sort asc() {
public Sort desc() {
return Sort.desc(name);
}
+
}
diff --git a/api/src/main/java/jakarta/data/metamodel/impl/TextAttributeRecord.java b/api/src/main/java/jakarta/data/metamodel/impl/TextAttributeRecord.java
index 4fe325751..720df286d 100644
--- a/api/src/main/java/jakarta/data/metamodel/impl/TextAttributeRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/impl/TextAttributeRecord.java
@@ -47,4 +47,5 @@ public Sort ascIgnoreCase() {
public Sort descIgnoreCase() {
return Sort.descIgnoreCase(name);
}
+
}
diff --git a/api/src/main/java/jakarta/data/page/CursoredPage.java b/api/src/main/java/jakarta/data/page/CursoredPage.java
index 272409bb8..d8b7a51c0 100644
--- a/api/src/main/java/jakarta/data/page/CursoredPage.java
+++ b/api/src/main/java/jakarta/data/page/CursoredPage.java
@@ -17,9 +17,10 @@
*/
package jakarta.data.page;
-import jakarta.data.repository.OrderBy;
import jakarta.data.Order;
import jakarta.data.Sort;
+import jakarta.data.repository.OrderBy;
+
import java.util.NoSuchElementException;
/**
@@ -208,4 +209,4 @@ public interface CursoredPage extends Page {
*/
@Override
PageRequest previousPageRequest();
-}
\ No newline at end of file
+}
diff --git a/api/src/main/java/jakarta/data/repository/OrderBy.java b/api/src/main/java/jakarta/data/repository/OrderBy.java
index cd8db4988..9b2044799 100644
--- a/api/src/main/java/jakarta/data/repository/OrderBy.java
+++ b/api/src/main/java/jakarta/data/repository/OrderBy.java
@@ -19,6 +19,7 @@
import jakarta.data.Order;
import jakarta.data.Sort;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java
index f52e8862f..eec8ab95a 100644
--- a/api/src/main/java/module-info.java
+++ b/api/src/main/java/module-info.java
@@ -1066,6 +1066,7 @@
* of the repository operation.
*/
module jakarta.data {
+ requires java.management;
exports jakarta.data;
exports jakarta.data.metamodel;
exports jakarta.data.metamodel.impl;
@@ -1075,4 +1076,7 @@
exports jakarta.data.exceptions;
opens jakarta.data.repository;
exports jakarta.data.spi;
-}
\ No newline at end of file
+ opens jakarta.data.metamodel;
+ opens jakarta.data;
+ opens jakarta.data.metamodel.impl;
+}
diff --git a/api/src/test/java/jakarta/data/CompositeRestrictionRecordTest.java b/api/src/test/java/jakarta/data/CompositeRestrictionRecordTest.java
new file mode 100644
index 000000000..55b020e69
--- /dev/null
+++ b/api/src/test/java/jakarta/data/CompositeRestrictionRecordTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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 org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+
+class CompositeRestrictionRecordTest {
+
+ @Test
+ void shouldCreateAllCompositeRestriction() {
+ Restriction restriction1 = Restrict.equalTo("value1", "field1");
+ Restriction restriction2 = Restrict.lessThan(10, "field2");
+
+ CompositeRestrictionRecord composite = CompositeRestrictionRecord.all(restriction1, restriction2);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(composite.type()).isEqualTo(CompositeRestrictionType.ALL);
+ soft.assertThat(composite.restrictions()).containsExactly(restriction1, restriction2);
+ });
+ }
+
+ @Test
+ void shouldCreateAnyCompositeRestriction() {
+ Restriction restriction1 = Restrict.greaterThan(5, "field1");
+ Restriction restriction2 = Restrict.in(Set.of(1, 2, 3), "field2");
+
+ CompositeRestrictionRecord composite = CompositeRestrictionRecord.any(restriction1, restriction2);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(composite.type()).isEqualTo(CompositeRestrictionType.ANY);
+ soft.assertThat(composite.restrictions()).containsExactly(restriction1, restriction2);
+ });
+ }
+
+ @Test
+ void shouldBeIterable() {
+ Restriction restriction1 = Restrict.equalTo("value1", "field1");
+ Restriction restriction2 = Restrict.lessThan(10, "field2");
+
+ CompositeRestrictionRecord composite = CompositeRestrictionRecord.all(restriction1, restriction2);
+
+ assertThat(composite).containsExactly(restriction1, restriction2);
+ }
+
+ @Test
+ void shouldReturnImmutableRestrictionsList() {
+ Restriction restriction1 = Restrict.equalTo("value1", "field1");
+ Restriction restriction2 = Restrict.lessThan(10, "field2");
+
+ CompositeRestrictionRecord composite = CompositeRestrictionRecord.all(restriction1, restriction2);
+
+ assertThatThrownBy(() -> composite.restrictions().add(Restrict.greaterThan(15, "field3")))
+ .isInstanceOf(UnsupportedOperationException.class);
+ }
+
+ @Test
+ void shouldSupportEmptyRestrictions() {
+ CompositeRestrictionRecord composite = CompositeRestrictionRecord.all();
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(composite.type()).isEqualTo(CompositeRestrictionType.ALL);
+ soft.assertThat(composite.restrictions()).isEmpty();
+ });
+ }
+
+ @Test
+ void shouldCombineDifferentRestrictionTypes() {
+ Restriction restriction1 = Restrict.contains("text", "field1");
+ Restriction restriction2 = Restrict.startsWith("prefix", "field2");
+ Restriction restriction3 = Restrict.endsWith("suffix", "field3");
+
+ CompositeRestrictionRecord composite = CompositeRestrictionRecord.all(restriction1, restriction2, restriction3);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(composite.type()).isEqualTo(CompositeRestrictionType.ALL);
+ soft.assertThat(composite.restrictions()).containsExactly(restriction1, restriction2, restriction3);
+ });
+ }
+}
diff --git a/api/src/test/java/jakarta/data/PatternTest.java b/api/src/test/java/jakarta/data/PatternTest.java
new file mode 100644
index 000000000..1eba8222e
--- /dev/null
+++ b/api/src/test/java/jakarta/data/PatternTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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 org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+class PatternTest {
+
+ @Test
+ void shouldCreateExactMatchPattern() {
+ Pattern pattern = Pattern.is("Java Guide");
+ assertThat(pattern.value()).isEqualTo("Java Guide");
+ assertThat(pattern.caseSensitive()).isTrue();
+ }
+
+ @Test
+ void shouldCreateCustomMatchPattern() {
+ Pattern pattern = Pattern.matches("Ja%_a");
+ assertThat(pattern.value()).isEqualTo("Ja%_a");
+ assertThat(pattern.caseSensitive()).isTrue();
+ }
+
+ @Test
+ void shouldCreateCustomWildcardPattern() {
+ Pattern pattern = Pattern.matches("Ja?a*", '?', '*');
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(pattern.value()).isEqualTo("Ja_a%");
+ soft.assertThat(pattern.caseSensitive()).isTrue();
+ });
+ }
+
+ @Test
+ void shouldCreatePrefixMatchPattern() {
+ Pattern pattern = Pattern.startsWith("Hibernate");
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(pattern.value()).isEqualTo("Hibernate%");
+ soft.assertThat(pattern.caseSensitive()).isTrue();
+ });
+ }
+
+ @Test
+ void shouldCreateSuffixMatchPattern() {
+ Pattern pattern = Pattern.endsWith("Guide");
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(pattern.value()).isEqualTo("%Guide");
+ soft.assertThat(pattern.caseSensitive()).isTrue();
+ });
+ }
+
+ @Test
+ void shouldCreateSubstringMatchPattern() {
+ Pattern pattern = Pattern.contains("Java");
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(pattern.value()).isEqualTo("%Java%");
+ soft.assertThat(pattern.caseSensitive()).isTrue();
+ });
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "Jav_%,Jav\\_\\%",
+ "Hello%World,Hello\\%World",
+ "Special_Chars,Special\\_Chars"
+ })
+ void shouldEscapeSpecialCharacters(String input, String expected) {
+ Pattern pattern = Pattern.is(input);
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(pattern.value()).isEqualTo(expected);
+ soft.assertThat(pattern.caseSensitive()).isTrue();
+ });
+ }
+}
diff --git a/api/src/test/java/jakarta/data/RestrictTest.java b/api/src/test/java/jakarta/data/RestrictTest.java
new file mode 100644
index 000000000..0d02a13aa
--- /dev/null
+++ b/api/src/test/java/jakarta/data/RestrictTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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 org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RestrictTest {
+
+ @Test
+ void shouldCreateBasicEqualityRestriction() {
+ Restriction restriction = Restrict.equalTo("Java Guide", "title");
+
+ assertThat(restriction).isInstanceOf(BasicRestriction.class);
+ BasicRestriction basic = (BasicRestriction) restriction;
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(basic.field()).isEqualTo("title");
+ soft.assertThat(basic.operator()).isEqualTo(Operator.EQUAL);
+ soft.assertThat(basic.value()).isEqualTo("Java Guide");
+ });
+ }
+
+ @Test
+ void shouldCreateLessThanRestriction() {
+ Restriction restriction = Restrict.lessThan(100, "price");
+
+ assertThat(restriction).isInstanceOf(BasicRestriction.class);
+ BasicRestriction basic = (BasicRestriction) restriction;
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(basic.field()).isEqualTo("price");
+ soft.assertThat(basic.operator()).isEqualTo(Operator.LESS_THAN);
+ soft.assertThat(basic.value()).isEqualTo(100);
+ });
+ }
+
+ @Test
+ void shouldCreateGreaterThanRestriction() {
+ Restriction restriction = Restrict.greaterThan(2020, "year");
+
+ assertThat(restriction).isInstanceOf(BasicRestriction.class);
+ BasicRestriction basic = (BasicRestriction) restriction;
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(basic.field()).isEqualTo("year");
+ soft.assertThat(basic.operator()).isEqualTo(Operator.GREATER_THAN);
+ soft.assertThat(basic.value()).isEqualTo(2020);
+ });
+ }
+
+ @Test
+ void shouldCreateInRestriction() {
+ Restriction restriction = Restrict.in(Set.of("Java", "Spring"), "title");
+
+ assertThat(restriction).isInstanceOf(BasicRestriction.class);
+ BasicRestriction basic = (BasicRestriction) restriction;
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(basic.field()).isEqualTo("title");
+ soft.assertThat(basic.operator()).isEqualTo(Operator.IN);
+ soft.assertThat(basic.value()).isInstanceOf(Set.class);
+ Set values = (Set) basic.value();
+ soft.assertThat(values).containsExactlyInAnyOrder("Java", "Spring");
+ });
+ }
+
+ @Test
+ void shouldCreateContainsRestriction() {
+ Restriction restriction = Restrict.contains("Hibernate", "title");
+
+ assertThat(restriction).isInstanceOf(BasicRestriction.class);
+ BasicRestriction basic = (BasicRestriction) restriction;
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(basic.field()).isEqualTo("title");
+ soft.assertThat(basic.operator()).isEqualTo(Operator.LIKE);
+ soft.assertThat(basic.value()).isEqualTo("%Hibernate%");
+ });
+ }
+
+ @Test
+ void shouldCreateStartsWithRestriction() {
+ Restriction restriction = Restrict.startsWith("Hibernate", "title");
+
+ assertThat(restriction).isInstanceOf(BasicRestriction.class);
+ BasicRestriction basic = (BasicRestriction) restriction;
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(basic.field()).isEqualTo("title");
+ soft.assertThat(basic.operator()).isEqualTo(Operator.LIKE);
+ soft.assertThat(basic.value()).isEqualTo("Hibernate%");
+ });
+ }
+
+ @Test
+ void shouldCreateEndsWithRestriction() {
+ Restriction restriction = Restrict.endsWith("Guide", "title");
+
+ assertThat(restriction).isInstanceOf(BasicRestriction.class);
+ BasicRestriction basic = (BasicRestriction) restriction;
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(basic.field()).isEqualTo("title");
+ soft.assertThat(basic.operator()).isEqualTo(Operator.LIKE);
+ soft.assertThat(basic.value()).isEqualTo("%Guide");
+ });
+ }
+
+ @Test
+ void shouldCreateCompositeAllRestriction() {
+ Restriction restriction = Restrict.all(
+ Restrict.equalTo("Java Guide", "title"),
+ Restrict.greaterThan(2020, "publicationYear")
+ );
+
+ assertThat(restriction).isInstanceOf(CompositeRestriction.class);
+ CompositeRestriction composite = (CompositeRestriction) restriction;
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(composite.type()).isEqualTo(CompositeRestrictionType.ALL);
+ soft.assertThat(composite.restrictions()).hasSize(2);
+ soft.assertThat(composite.restrictions().get(0)).isInstanceOf(BasicRestriction.class);
+ soft.assertThat(composite.restrictions().get(1)).isInstanceOf(BasicRestriction.class);
+ });
+ }
+
+ @Test
+ void shouldCreateCompositeAnyRestriction() {
+ Restriction restriction = Restrict.any(
+ Restrict.contains("Java", "title"),
+ Restrict.lessThan(500, "pages")
+ );
+
+ assertThat(restriction).isInstanceOf(CompositeRestriction.class);
+ CompositeRestriction composite = (CompositeRestriction) restriction;
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(composite.type()).isEqualTo(CompositeRestrictionType.ANY);
+ soft.assertThat(composite.restrictions()).hasSize(2);
+ soft.assertThat(composite.restrictions().get(0)).isInstanceOf(BasicRestriction.class);
+ soft.assertThat(composite.restrictions().get(1)).isInstanceOf(BasicRestriction.class);
+ });
+ }
+}
diff --git a/api/src/test/java/jakarta/data/metamodel/RangeTest.java b/api/src/test/java/jakarta/data/metamodel/RangeTest.java
new file mode 100644
index 000000000..8de44cc8f
--- /dev/null
+++ b/api/src/test/java/jakarta/data/metamodel/RangeTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDate;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RangeTest {
+
+
+ @Test
+ void shouldCreateInclusiveBetweenRange() {
+ Range range = Range.between("publicationDate", LocalDate.of(2020, 1, 1), LocalDate.of(2023, 1, 1));
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("publicationDate");
+ soft.assertThat(range.operator()).isEqualTo(Operator.BETWEEN);
+ soft.assertThat(range.value()).isEqualTo(List.of(LocalDate.of(2020, 1, 1), LocalDate.of(2023, 1, 1)));
+ });
+ }
+
+ @Test
+ void shouldCreateInclusiveFromRange() {
+ Range range = Range.from("rating", 4.0);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("rating");
+ soft.assertThat(range.operator()).isEqualTo(Operator.GREATER_THAN_EQUAL);
+ soft.assertThat(range.value()).isEqualTo(4.0);
+ });
+ }
+
+ @Test
+ void shouldCreateInclusiveToRange() {
+ Range range = Range.to("price", 100);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("price");
+ soft.assertThat(range.operator()).isEqualTo(Operator.LESS_THAN_EQUAL);
+ soft.assertThat(range.value()).isEqualTo(100);
+ });
+ }
+
+ @Test
+ void shouldCreateExclusiveAboveRange() {
+ Range range = Range.above("rating", 4.0);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("rating");
+ soft.assertThat(range.operator()).isEqualTo(Operator.GREATER_THAN);
+ soft.assertThat(range.value()).isEqualTo(4.0);
+ });
+ }
+
+ @Test
+ void shouldCreateExclusiveBelowRange() {
+ Range range = Range.below("price", 200.0);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("price");
+ soft.assertThat(range.operator()).isEqualTo(Operator.LESS_THAN);
+ soft.assertThat(range.value()).isEqualTo(200.0);
+ });
+ }
+
+ @Test
+ void shouldHandleNullLowerBoundForToRange() {
+ Range range = Range.to("price", 200.0);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("price");
+ soft.assertThat(range.operator()).isEqualTo(Operator.LESS_THAN_EQUAL);
+ soft.assertThat(range.lowerBound()).isNull();
+ soft.assertThat(range.upperBound()).isEqualTo(200.0);
+ });
+ }
+
+ @Test
+ void shouldHandleNullUpperBoundForFromRange() {
+ Range range = Range.from("price", 50.0);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("price");
+ soft.assertThat(range.operator()).isEqualTo(Operator.GREATER_THAN_EQUAL);
+ soft.assertThat(range.lowerBound()).isEqualTo(50.0);
+ soft.assertThat(range.upperBound()).isNull();
+ });
+ }
+
+ @Test
+ void shouldHandleBothBoundsForBetweenRange() {
+ Range range = Range.between("price", 50.0, 200.0);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("price");
+ soft.assertThat(range.operator()).isEqualTo(Operator.BETWEEN);
+ soft.assertThat(range.lowerBound()).isEqualTo(50.0);
+ soft.assertThat(range.upperBound()).isEqualTo(200.0);
+ });
+ }
+
+ @Test
+ void shouldHandleOpenRangeForAbove() {
+ Range range = Range.above("age", 18);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("age");
+ soft.assertThat(range.operator()).isEqualTo(Operator.GREATER_THAN);
+ soft.assertThat(range.lowerBound()).isEqualTo(18);
+ soft.assertThat(range.upperBound()).isNull();
+ soft.assertThat(range.open()).isTrue();
+ });
+ }
+
+ @Test
+ void shouldHandleOpenRangeForBelow() {
+ Range range = Range.below("age", 65);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(range.field()).isEqualTo("age");
+ soft.assertThat(range.operator()).isEqualTo(Operator.LESS_THAN);
+ soft.assertThat(range.lowerBound()).isNull();
+ soft.assertThat(range.upperBound()).isEqualTo(65);
+ soft.assertThat(range.open()).isTrue();
+ });
+ }
+}
diff --git a/api/src/test/java/jakarta/data/metamodel/impl/BasicRestrictionRecordTest.java b/api/src/test/java/jakarta/data/metamodel/impl/BasicRestrictionRecordTest.java
new file mode 100644
index 000000000..b151341f4
--- /dev/null
+++ b/api/src/test/java/jakarta/data/metamodel/impl/BasicRestrictionRecordTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.impl;
+
+import jakarta.data.Operator;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Test;
+
+
+class BasicRestrictionRecordTest {
+ @Test
+ void shouldCreateBasicEqualityRestriction() {
+ BasicRestrictionRecord restriction = new BasicRestrictionRecord<>("title", Operator.EQUAL, "Java Guide");
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(restriction.field()).isEqualTo("title");
+ soft.assertThat(restriction.operator()).isEqualTo(Operator.EQUAL);
+ soft.assertThat(restriction.value()).isEqualTo("Java Guide");
+ });
+ }
+
+ @Test
+ void shouldCreateGreaterThanRestriction() {
+ BasicRestrictionRecord restriction = new BasicRestrictionRecord<>("price", Operator.GREATER_THAN, 100);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(restriction.field()).isEqualTo("price");
+ soft.assertThat(restriction.operator()).isEqualTo(Operator.GREATER_THAN);
+ soft.assertThat(restriction.value()).isEqualTo(100);
+ });
+ }
+
+ @Test
+ void shouldCreateLessThanRestriction() {
+ BasicRestrictionRecord restriction = new BasicRestrictionRecord<>("quantity", Operator.LESS_THAN, 50);
+
+ SoftAssertions.assertSoftly(soft -> {
+ soft.assertThat(restriction.field()).isEqualTo("quantity");
+ soft.assertThat(restriction.operator()).isEqualTo(Operator.LESS_THAN);
+ soft.assertThat(restriction.value()).isEqualTo(50);
+ });
+ }
+
+}