Repository: jakartaee/data
Pull request #990 (2025-02-24 20:40:32)

negatable Constraints and temporarily omit ignoreCase, then restore methods, also fix some type parameters
This PR restores the methods that were lost in #975 after swapping where we keep the information for negation and ignoreCase.

Making Constraints negatable is important because it its a valid scenario to want to be able to supply a negated constraint to a method like `List<Book> search(Constraint<String> author)`. For example, using a NotNull Constraint to search only for books that do have an author.  Once we have the negated constraints, then we can also remove Operator.

I'm proposing to move ignoreCase in the opposite direction, taking it off of the `Like` constraint, and moving it back to `TextRestriction`.  This is because ignoring case is broader than just `Like` and also applies to a bunch of other Constraints.  Then we need to answer how does a repository method indicate that case should be ignored for a Constraint parameter.  The solution in this PR is to add an `IgnoreCase` annotation that can supply the information upfront when the repository method is written, which should be fine because case sensitivity concerns the data itself. It also frees the repository user of needing to specify ignoreCase everywhere and the risk of mistakes from forgetting to do it in one place.

With these updates, it was then possible to bring back the lost methods.

While working on this, I also noticed a variety of cleanup work that could be done (such as removing hashCode/equals from records that already implement those methods for us), but I did not attempt to cover that in this PR.

Moving ignoreCase to Restriction turned out to be controversial and needs further discussion.  It was decided that we can move ahead with these other changes but will defer the decision of where to put ignoreCase.  So for now, it will be omitted.  After more discussion happens, a follow-on PR will add it back in wherever it is best decided to put it.

gavinking: Note that in #988 I proposed a simple way to deal with case-insensitive matching which would also eliminate the need for `LikeLiteralRecord`.
gavinking: > I'm proposing to move `ignoreCase` in the opposite direction, taking it off of the `Like` constraint, and moving it back to `TextRestriction`

I'm proposing to move it even further back, all the way to `TextAttribute`. i.e. `_Book.title.ignoreCase().equals("....")`
njr-11: > > I'm proposing to move `ignoreCase` in the opposite direction, taking it off of the `Like` constraint, and moving it back to `TextRestriction`
> 
> I'm proposing to move it even further back, all the way to `TextAttribute`. i.e. `_Book.title.ignoreCase().equals("....")`

Yes, I think it's the right idea to move it off of Constraint/Like, but if you move it all the way to `TextAttribute`, then users who are using the `Restrict` API only and not the static metamodel will be unable to supply restrictions that ignore case.
gavinking: > users who are using the Restrict API only and not the static metamodel will be unable to supply restrictions that ignore case.

Well we can easily provide them with a factory method for that.
njr-11: > > users who are using the Restrict API only and not the static metamodel will be unable to supply restrictions that ignore case.
> 
> Well we can easily provide them with a factory method for that.

It's not clear to me how that would work. I don't see where the value would be stored. In this case, they have a `TextRestriction`, not a `TextAttribute` to store the boolean.  I think you would need an approach like what I've done here that stores the boolean on `TextRestriction`.
gavinking: > It's not clear to me how that would work. I don't see where the value would be stored. I

See my comments on the other thread. It works. What doesn't work is `Restrict` as it exists _today_.
njr-11: > See my comments on the other thread. It works. What doesn't work is `Restrict` as it exists _today_.

On the other thread we concluded that the `Restrict` methods that don't work properly with `.negated()` and `.ignoreCase()` should be removed or moved to internal.  This PR now does that as well and is ready for review.

Patch:
diff --git a/api/src/main/java/jakarta/data/metamodel/BasicAttribute.java b/api/src/main/java/jakarta/data/metamodel/BasicAttribute.java
index 85e805de..a56c50e7 100644
--- a/api/src/main/java/jakarta/data/metamodel/BasicAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/BasicAttribute.java
@@ -18,7 +18,11 @@
 package jakarta.data.metamodel;
 
 import jakarta.data.metamodel.constraint.Constraint;
-import jakarta.data.metamodel.restrict.Restrict;
+import jakarta.data.metamodel.constraint.In;
+import jakarta.data.metamodel.constraint.NotEqualTo;
+import jakarta.data.metamodel.constraint.NotIn;
+import jakarta.data.metamodel.constraint.NotNull;
+import jakarta.data.metamodel.constraint.Null;
 import jakarta.data.metamodel.restrict.Restriction;
 
 import java.util.Objects;
@@ -34,33 +38,33 @@ import java.util.Set;
 public interface BasicAttribute<T,V> extends Attribute<T> {
 
     default Restriction<T> equalTo(V value) {
-        return Restrict.equalTo(value, name());
+        return new BasicRestrictionRecord<>(name(), Constraint.equalTo(value));
     }
 
     default Restriction<T> in(Set<V> values) {
         if (values == null || values.isEmpty())
             throw new IllegalArgumentException("values are required");
 
-        return Restrict.in(values, name());
+        return new BasicRestrictionRecord<>(name(), In.values(values));
     }
 
     default Restriction<T> isNull() {
-        return Restrict.isNull(name());
+        return new BasicRestrictionRecord<>(name(), Null.instance());
     }
 
     default Restriction<T> notEqualTo(V value) {
-        return Restrict.notEqualTo(value, name());
+        return new BasicRestrictionRecord<>(name(), NotEqualTo.value(value));
     }
 
     default Restriction<T> notIn(Set<V> values) {
         if (values == null || values.isEmpty())
             throw new IllegalArgumentException("values are required");
 
-        return Restrict.notIn(values, name());
+        return new BasicRestrictionRecord<>(name(), NotIn.values(values));
     }
 
     default Restriction<T> notNull() {
-        return Restrict.notNull(name());
+        return new BasicRestrictionRecord<>(name(), NotNull.instance());
     }
 
     /**
@@ -85,7 +89,7 @@ public interface BasicAttribute<T,V> extends Attribute<T> {
     }
 
     default Restriction<T> restrict(Constraint<V> constraint) {
-        return Restrict.restrict(constraint, name());
+        return new BasicRestrictionRecord<>(name(), constraint);
     }
 }
 
diff --git a/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestrictionRecord.java b/api/src/main/java/jakarta/data/metamodel/BasicRestrictionRecord.java
similarity index 62%
rename from api/src/main/java/jakarta/data/metamodel/restrict/BasicRestrictionRecord.java
rename to api/src/main/java/jakarta/data/metamodel/BasicRestrictionRecord.java
index 5a1c244d..f18854db 100644
--- a/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestrictionRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/BasicRestrictionRecord.java
@@ -15,36 +15,28 @@
  *
  *  SPDX-License-Identifier: Apache-2.0
  */
-package jakarta.data.metamodel.restrict;
+package jakarta.data.metamodel;
 
 // Internal implementation class.
 // The proper way for users to obtain instances is via
 // the static metamodel or Restrict.* methods 
 
 import jakarta.data.metamodel.constraint.Constraint;
+import jakarta.data.metamodel.restrict.BasicRestriction;
 
 import java.util.Objects;
 
-record BasicRestrictionRecord<T>(String attribute, Constraint<?> constraint, boolean negated)
-        implements BasicRestriction<T> {
+record BasicRestrictionRecord<T, V>(String attribute, Constraint<V> constraint)
+        implements BasicRestriction<T, V> {
 
     BasicRestrictionRecord {
         Objects.requireNonNull(attribute, "Attribute must not be null");
         Objects.requireNonNull(constraint, "Constraint must not be null");
     }
 
-    public BasicRestrictionRecord(String attribute, Constraint<?> constraint) {
-        this(attribute, constraint, false);
-    }
-
-    @Override
-    public Operator comparison() {
-        return negated ? constraint.operator().negate() : constraint.operator();
-    }
-
     @Override
-    public BasicRestriction<T> negate() {
-        return new BasicRestrictionRecord<>(attribute, constraint, !negated);
+    public BasicRestriction<T, V> negate() {
+        return new BasicRestrictionRecord<>(attribute, constraint.negate());
     }
 
     /**
@@ -56,12 +48,6 @@ record BasicRestrictionRecord<T>(String attribute, Constraint<?> constraint, boo
      */
     @Override
     public String toString() {
-        final Operator comparison = comparison();
-        final String op = comparison.asQueryLanguage();
-        return switch (comparison.arity()) {
-            case 1 -> attribute + ' ' + op;
-            case 2,3 -> attribute + ' ' + constraint;
-            default -> throw new UnsupportedOperationException("Unexpected arity");
-        };
+        return attribute + ' ' + constraint;
     }
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java b/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java
index 702ffc73..0143c462 100644
--- a/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java
@@ -20,7 +20,12 @@ package jakarta.data.metamodel;
 import java.util.Objects;
 
 import jakarta.data.Sort;
-import jakarta.data.metamodel.restrict.Restrict;
+import jakarta.data.metamodel.constraint.Between;
+import jakarta.data.metamodel.constraint.GreaterThan;
+import jakarta.data.metamodel.constraint.GreaterThanOrEqual;
+import jakarta.data.metamodel.constraint.LessThan;
+import jakarta.data.metamodel.constraint.LessThanOrEqual;
+import jakarta.data.metamodel.constraint.NotBetween;
 import jakarta.data.metamodel.restrict.Restriction;
 
 /**
@@ -58,27 +63,27 @@ public interface ComparableAttribute<T,V extends Comparable<V>>
         extends BasicAttribute<T,V>, SortableAttribute<T> {
 
     default Restriction<T> between(V min, V max) {
-        return Restrict.between(min, max, name());
+        return new BasicRestrictionRecord<>(name(), Between.bounds(min, max));
     }
 
     default Restriction<T> notBetween(V min, V max) {
-        return Restrict.notBetween(min, max, name());
+        return new BasicRestrictionRecord<>(name(), NotBetween.bounds(min, max));
     }
 
     default Restriction<T> greaterThan(V value) {
-        return Restrict.greaterThan(value, name());
+        return new BasicRestrictionRecord<>(name(), GreaterThan.bound(value));
     }
 
     default Restriction<T> greaterThanEqual(V value) {
-        return Restrict.greaterThanEqual(value, name());
+        return new BasicRestrictionRecord<>(name(), GreaterThanOrEqual.min(value));
     }
 
     default Restriction<T> lessThan(V value) {
-        return Restrict.lessThan(value, name());
+        return new BasicRestrictionRecord<>(name(), LessThan.bound(value));
     }
 
     default Restriction<T> lessThanEqual(V value) {
-        return Restrict.lessThanEqual(value, name());
+        return new BasicRestrictionRecord<>(name(), LessThanOrEqual.max(value));
     }
 
     /**
diff --git a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
index ff2c75ca..8f6f1485 100644
--- a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
@@ -20,9 +20,17 @@ package jakarta.data.metamodel;
 import java.util.Objects;
 
 import jakarta.data.Sort;
+import jakarta.data.metamodel.constraint.Between;
+import jakarta.data.metamodel.constraint.EqualTo;
+import jakarta.data.metamodel.constraint.GreaterThan;
+import jakarta.data.metamodel.constraint.GreaterThanOrEqual;
+import jakarta.data.metamodel.constraint.LessThan;
+import jakarta.data.metamodel.constraint.LessThanOrEqual;
 import jakarta.data.metamodel.constraint.Like;
+import jakarta.data.metamodel.constraint.NotBetween;
+import jakarta.data.metamodel.constraint.NotEqualTo;
+import jakarta.data.metamodel.constraint.NotLike;
 import jakarta.data.metamodel.impl.TextAttributeRecord;
-import jakarta.data.metamodel.restrict.Restrict;
 import jakarta.data.metamodel.restrict.TextRestriction;
 
 /**
@@ -46,58 +54,93 @@ public interface TextAttribute<T> extends ComparableAttribute<T,String> {
      */
     Sort<T> descIgnoreCase();
 
+    @Override
+    default TextRestriction<T> between(String min, String max) {
+        return new TextRestrictionRecord<>(name(), Between.bounds(min, max));
+    }
+
     @Override
     default TextRestriction<T> equalTo(String value) {
-        return Restrict.equalTo(value, name());
+        return new TextRestrictionRecord<>(name(), EqualTo.value(value));
+    }
+
+    @Override
+    default TextRestriction<T> greaterThan(String value) {
+        return new TextRestrictionRecord<>(name(), GreaterThan.bound(value));
+    }
+
+    @Override
+    default TextRestriction<T> greaterThanEqual(String value) {
+        return new TextRestrictionRecord<>(name(), GreaterThanOrEqual.min(value));
+    }
+
+    @Override
+    default TextRestriction<T> lessThan(String value) {
+        return new TextRestrictionRecord<>(name(), LessThan.bound(value));
+    }
+
+    @Override
+    default TextRestriction<T> lessThanEqual(String value) {
+        return new TextRestrictionRecord<>(name(), LessThanOrEqual.max(value));
+    }
+
+    @Override
+    default TextRestriction<T> notBetween(String min, String max) {
+        return new TextRestrictionRecord<>(name(), NotBetween.bounds(min, max));
     }
 
     @Override
     default TextRestriction<T> notEqualTo(String value) {
-        return Restrict.notEqualTo(value, name());
+        return new TextRestrictionRecord<>(name(), NotEqualTo.value(value));
     }
 
     default TextRestriction<T> like(Like pattern) {
-        return Restrict.like(pattern, name());
+        return new TextRestrictionRecord<>(name(), pattern);
+
     }
 
     default TextRestriction<T> like(String pattern) {
-        return Restrict.like(pattern, name());
+        return new TextRestrictionRecord<>(name(), Like.pattern(pattern));
     }
 
     default TextRestriction<T> like(String pattern, char charWildcard, char stringWildcard) {
-        return Restrict.like(pattern, charWildcard, stringWildcard, name());
+        Like constraint = Like.pattern(pattern, charWildcard, stringWildcard);
+        return new TextRestrictionRecord<>(name(), constraint);
     }
 
     default TextRestriction<T> like(String pattern, char charWildcard, char stringWildcard, char escape) {
-        return Restrict.like(pattern, charWildcard, stringWildcard, escape, name());
+        Like constraint = Like.pattern(pattern, charWildcard, stringWildcard, escape);
+        return new TextRestrictionRecord<>(name(), constraint);
     }
 
     default TextRestriction<T> notLike(String pattern) {
-        return Restrict.notLike(pattern, name());
+        return new TextRestrictionRecord<>(name(), NotLike.pattern(pattern));
     }
 
     default TextRestriction<T> notLike(String pattern, char charWildcard, char stringWildcard) {
-        return Restrict.notLike(pattern, charWildcard, stringWildcard, name());
+        NotLike constraint = NotLike.pattern(pattern, charWildcard, stringWildcard);
+        return new TextRestrictionRecord<>(name(), constraint);
     }
 
     default TextRestriction<T> notLike(String pattern, char charWildcard, char stringWildcard, char escape) {
-        return Restrict.notLike(pattern, charWildcard, stringWildcard, escape, name());
+        NotLike constraint = NotLike.pattern(pattern, charWildcard, stringWildcard, escape);
+        return new TextRestrictionRecord<>(name(), constraint);
     }
 
     default TextRestriction<T> contains(String substring) {
-        return Restrict.contains(substring, name());
+        return new TextRestrictionRecord<>(name(), Like.substring(substring));
     }
 
     default TextRestriction<T> notContains(String substring) {
-        return Restrict.notContains(substring, name());
+        return new TextRestrictionRecord<>(name(), NotLike.substring(substring));
     }
 
     default TextRestriction<T> endsWith(String suffix) {
-        return Restrict.endsWith(suffix, name());
+        return new TextRestrictionRecord<>(name(), Like.suffix(suffix));
     }
 
     default TextRestriction<T> notEndsWith(String suffix) {
-        return Restrict.notEndsWith(suffix, name());
+        return new TextRestrictionRecord<>(name(), NotLike.suffix(suffix));
     }
 
     /**
@@ -117,11 +160,11 @@ public interface TextAttribute<T> extends ComparableAttribute<T,String> {
     }
 
     default TextRestriction<T> startsWith(String prefix) {
-        return Restrict.startsWith(prefix, name());
+        return new TextRestrictionRecord<>(name(), Like.prefix(prefix));
     }
 
     default TextRestriction<T> notStartsWith(String prefix) {
-        return Restrict.notStartsWith(prefix, name());
+        return new TextRestrictionRecord<>(name(), NotLike.prefix(prefix));
     }
 
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/restrict/TextRestrictionRecord.java b/api/src/main/java/jakarta/data/metamodel/TextRestrictionRecord.java
similarity index 61%
rename from api/src/main/java/jakarta/data/metamodel/restrict/TextRestrictionRecord.java
rename to api/src/main/java/jakarta/data/metamodel/TextRestrictionRecord.java
index cf0963cc..1f010b8c 100644
--- a/api/src/main/java/jakarta/data/metamodel/restrict/TextRestrictionRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/TextRestrictionRecord.java
@@ -15,13 +15,14 @@
  *
  *  SPDX-License-Identifier: Apache-2.0
  */
-package jakarta.data.metamodel.restrict;
+package jakarta.data.metamodel;
 
-import jakarta.data.metamodel.constraint.Like;
+import jakarta.data.metamodel.constraint.Constraint;
+import jakarta.data.metamodel.restrict.TextRestriction;
 
 import java.util.Objects;
 
-record TextRestrictionRecord<T>(String attribute, Like constraint, boolean negated)
+record TextRestrictionRecord<T>(String attribute, Constraint<String> constraint)
         implements TextRestriction<T> {
 
     TextRestrictionRecord {
@@ -29,31 +30,17 @@ record TextRestrictionRecord<T>(String attribute, Like constraint, boolean negat
         Objects.requireNonNull(constraint, "Constraint must not be null");
     }
 
-    public TextRestrictionRecord(String attribute, Like range) {
-        this(attribute, range, false);
-    }
-
-    @Override
-    public Operator comparison() {
-        return negated ? constraint.operator().negate() : constraint.operator();
-    }
-
     @Override
     public TextRestrictionRecord<T> negate() {
-        return new TextRestrictionRecord<>(attribute, constraint, !negated);
-    }
-
-    @Override
-    public TextRestrictionRecord<T> ignoreCase() {
-        return new TextRestrictionRecord<>(attribute, constraint.ignoreCase(), negated);
+        return new TextRestrictionRecord<>(attribute, constraint.negate());
     }
 
     /**
-     * Textual representation of a basic restriction.
+     * Textual representation of a text restriction.
      * For example,
-     * <pre>price < 50.0</pre>
+     * <pre>title LIKE '%JAKARTA EE%'</pre>
      *
-     * @return textual representation of a basic restriction.
+     * @return textual representation of a text restriction.
      */
     @Override
     public String toString() {
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/Between.java b/api/src/main/java/jakarta/data/metamodel/constraint/Between.java
index 8c9aeddf..3c6750f7 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/Between.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/Between.java
@@ -18,6 +18,11 @@
 package jakarta.data.metamodel.constraint;
 
 public interface Between<T extends Comparable<T>> extends Constraint<T> {
+
+    static <T extends Comparable<T>> Between<T> bounds(T lower, T upper) {
+        return new BetweenRecord<>(lower, upper);
+    }
+
     T lowerBound();
     T upperBound();
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/BetweenRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/BetweenRecord.java
index b99d5a38..729149f2 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/BetweenRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/BetweenRecord.java
@@ -17,8 +17,6 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 import java.util.Objects;
 
 record BetweenRecord<T extends Comparable<T>>(T lowerBound, T upperBound)
@@ -28,11 +26,6 @@ record BetweenRecord<T extends Comparable<T>>(T lowerBound, T upperBound)
         Objects.requireNonNull(upperBound);
     }
 
-    @Override
-    public Operator operator() {
-        return Operator.BETWEEN;
-    }
-
     @Override
     public String toString() {
         return "BETWEEN " + lowerBound + " AND " + upperBound;
@@ -49,4 +42,9 @@ record BetweenRecord<T extends Comparable<T>>(T lowerBound, T upperBound)
     public int hashCode() {
         return Objects.hash(lowerBound, upperBound);
     }
+
+    @Override
+    public NotBetween<T> negate() {
+        return NotBetween.bounds(lowerBound, upperBound);
+    }
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/Constraint.java b/api/src/main/java/jakarta/data/metamodel/constraint/Constraint.java
index 5b80ca87..5487d6c4 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/Constraint.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/Constraint.java
@@ -17,12 +17,9 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 import java.util.Set;
 
 public interface Constraint<T> {
-    Operator operator();
 
     static <T> Constraint<T> equalTo(T value) {
         return new EqualToRecord<>(value);
@@ -61,4 +58,38 @@ public interface Constraint<T> {
         return new BetweenRecord<>(lowerBound, upperBound);
     }
 
+    Constraint<T> negate();
+
+    static <T extends Comparable<T>> Constraint<T> notBetween(T lowerBound, T upperBound) {
+        return NotBetween.bounds(lowerBound, upperBound);
+    }
+
+    static <T> NotEqualTo<T> notEqualTo(T value) {
+        return NotEqualTo.value(value);
+    }
+
+    @SafeVarargs
+    static <T> Constraint<T> notIn(T... values) {
+        return NotIn.values(values);
+    }
+
+    static <T> Constraint<T> notIn(Set<T> values) {
+        return NotIn.values(values);
+    }
+
+    static NotLike notLike(String pattern) {
+        return NotLike.pattern(pattern);
+    }
+
+    static NotLike notLike(String pattern, char charWildcard, char stringWildcard) {
+        return NotLike.pattern(pattern, charWildcard, stringWildcard);
+    }
+
+    static NotLike notLike(String pattern, char charWildcard, char stringWildcard, char escape) {
+        return NotLike.pattern(pattern, charWildcard, stringWildcard, escape);
+    }
+
+    static <T> Constraint<T> notNull() {
+        return NotNull.instance();
+    }
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/EqualTo.java b/api/src/main/java/jakarta/data/metamodel/constraint/EqualTo.java
index 6aa34624..7a9db74f 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/EqualTo.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/EqualTo.java
@@ -18,5 +18,10 @@
 package jakarta.data.metamodel.constraint;
 
 public interface EqualTo<T> extends Constraint<T> {
+
+    static <T> EqualTo<T> value(T value) {
+        return new EqualToRecord<>(value);
+    }
+
     T value();
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/EqualToRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/EqualToRecord.java
index 4d2835f9..9e1436e5 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/EqualToRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/EqualToRecord.java
@@ -17,8 +17,6 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 import java.util.Objects;
 
 record EqualToRecord<T>(T value) implements EqualTo<T> {
@@ -27,8 +25,8 @@ record EqualToRecord<T>(T value) implements EqualTo<T> {
     }
 
     @Override
-    public Operator operator() {
-        return Operator.EQUAL;
+    public NotEqualTo<T> negate() {
+        return NotEqualTo.value(value);
     }
 
     @Override
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThan.java b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThan.java
index bf691282..5701bb5a 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThan.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThan.java
@@ -18,5 +18,10 @@
 package jakarta.data.metamodel.constraint;
 
 public interface GreaterThan<T extends Comparable<T>> extends Constraint<T> {
+
+    static <T extends Comparable<T>> GreaterThan<T> bound(T lowerBound) {
+        return new GreaterThanRecord<>(lowerBound);
+    }
+
     T bound();
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqual.java b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqual.java
index 7151f80b..c4a15732 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqual.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqual.java
@@ -18,5 +18,10 @@
 package jakarta.data.metamodel.constraint;
 
 public interface GreaterThanOrEqual<T extends Comparable<T>> extends Constraint<T> {
+
+    static <T extends Comparable<T>> GreaterThanOrEqual<T> min(T minimum) {
+        return new GreaterThanOrEqualRecord<>(minimum);
+    }
+
     T bound();
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqualRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqualRecord.java
index d68085ab..010b384c 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqualRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqualRecord.java
@@ -17,8 +17,6 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 import java.util.Objects;
 
 record GreaterThanOrEqualRecord<T extends Comparable<T>>(T bound)
@@ -28,8 +26,8 @@ record GreaterThanOrEqualRecord<T extends Comparable<T>>(T bound)
     }
 
     @Override
-    public Operator operator() {
-        return Operator.GREATER_THAN_EQUAL;
+    public LessThan<T> negate() {
+        return LessThan.bound(bound);
     }
 
     @Override
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanRecord.java
index a02b6f00..e44fd1a4 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanRecord.java
@@ -17,8 +17,6 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 import java.util.Objects;
 
 record GreaterThanRecord<T extends Comparable<T>>(T bound)
@@ -28,8 +26,8 @@ record GreaterThanRecord<T extends Comparable<T>>(T bound)
     }
 
     @Override
-    public Operator operator() {
-        return Operator.GREATER_THAN;
+    public LessThanOrEqual<T> negate() {
+        return LessThanOrEqual.max(bound);
     }
 
     @Override
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/In.java b/api/src/main/java/jakarta/data/metamodel/constraint/In.java
index cb2697b9..37700490 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/In.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/In.java
@@ -20,5 +20,15 @@ package jakarta.data.metamodel.constraint;
 import java.util.Set;
 
 public interface In<T> extends Constraint<T> {
+
+    static <T> In<T> values(Set<T> values) {
+        return new InRecord<>(values);
+    }
+
+    @SafeVarargs
+    static <T> In<T> values(T... values) {
+        return new InRecord<>(Set.of(values));
+    }
+
     Set<T> values();
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/InRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/InRecord.java
index e4933460..b3d751bb 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/InRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/InRecord.java
@@ -17,8 +17,6 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 import java.util.Objects;
 import java.util.Set;
 
@@ -26,11 +24,9 @@ record InRecord<T>(Set<T> values) implements In<T> {
 
     public InRecord {
         Objects.requireNonNull(values, "Enumerated values cannot be null");
-    }
-
-    @Override
-    public Operator operator() {
-        return Operator.IN;
+        for (T value : values) {
+            Objects.requireNonNull(value, "value must not be null");
+        }
     }
 
     @Override
@@ -44,6 +40,11 @@ record InRecord<T>(Set<T> values) implements In<T> {
         return values.hashCode();
     }
 
+    @Override
+    public NotIn<T> negate() {
+        return NotIn.values(values);
+    }
+
     @Override
     public String toString() {
         return "IN " + values;
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/LessThan.java b/api/src/main/java/jakarta/data/metamodel/constraint/LessThan.java
index 3b532887..13f7ce73 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LessThan.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/LessThan.java
@@ -18,5 +18,10 @@
 package jakarta.data.metamodel.constraint;
 
 public interface LessThan<T extends Comparable<T>> extends Constraint<T> {
+
+    static <T extends Comparable<T>> LessThan<T> bound(T upperBound) {
+        return new LessThanRecord<>(upperBound);
+    }
+
     T bound();
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqual.java b/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqual.java
index a7cf8871..db6886ce 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqual.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqual.java
@@ -18,5 +18,10 @@
 package jakarta.data.metamodel.constraint;
 
 public interface LessThanOrEqual<T extends Comparable<T>> extends Constraint<T> {
+
+    static <T extends Comparable<T>> LessThanOrEqual<T> max(T maximum) {
+        return new LessThanOrEqualRecord<>(maximum);
+    }
+
     T bound();
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqualRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqualRecord.java
index 8a039be6..05df0c7d 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqualRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqualRecord.java
@@ -17,8 +17,6 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 import java.util.Objects;
 
 record LessThanOrEqualRecord<T extends Comparable<T>>(T bound)
@@ -28,8 +26,8 @@ record LessThanOrEqualRecord<T extends Comparable<T>>(T bound)
     }
 
     @Override
-    public Operator operator() {
-        return Operator.LESS_THAN_EQUAL;
+    public GreaterThan<T> negate() {
+        return GreaterThan.bound(bound);
     }
 
     @Override
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/LessThanRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/LessThanRecord.java
index 9a1589a1..7a3058bf 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LessThanRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/LessThanRecord.java
@@ -17,8 +17,6 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 import java.util.Objects;
 
 record LessThanRecord<T extends Comparable<T>>(T bound)
@@ -28,8 +26,8 @@ record LessThanRecord<T extends Comparable<T>>(T bound)
     }
 
     @Override
-    public Operator operator() {
-        return Operator.LESS_THAN;
+    public GreaterThanOrEqual<T> negate() {
+        return GreaterThanOrEqual.min(bound);
     }
 
     @Override
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/Like.java b/api/src/main/java/jakarta/data/metamodel/constraint/Like.java
index c29490d3..b1bdf1f9 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/Like.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/Like.java
@@ -18,37 +18,30 @@
 package jakarta.data.metamodel.constraint;
 
 public interface Like extends Constraint<String> {
-    String string();
-    boolean caseSensitive();
-    boolean pattern();
+    String pattern();
     Character escape();
-    Like ignoreCase();
-
-    static Like literal(String literal) {
-        return new LikeLiteralRecord(literal);
-    }
 
     static Like pattern(String pattern) {
-        return new LikePatternRecord(pattern);
+        return new LikeRecord(pattern);
     }
 
     static Like pattern(String pattern, char charWildcard, char stringWildcard) {
-        return new LikePatternRecord(pattern, charWildcard, stringWildcard);
+        return new LikeRecord(pattern, charWildcard, stringWildcard);
     }
 
     static Like pattern(String pattern, char charWildcard, char stringWildcard, char escape) {
-        return new LikePatternRecord(pattern, charWildcard, stringWildcard, escape);
+        return new LikeRecord(pattern, charWildcard, stringWildcard, escape);
     }
 
     static Like substring(String substring) {
-        return LikePatternRecord.substring(substring);
+        return LikeRecord.substring(substring);
     }
 
     static Like prefix(String prefix) {
-        return LikePatternRecord.prefix(prefix);
+        return LikeRecord.prefix(prefix);
     }
 
     static Like suffix(String suffix) {
-        return LikePatternRecord.suffix(suffix);
+        return LikeRecord.suffix(suffix);
     }
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/LikeLiteralRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/LikeLiteralRecord.java
deleted file mode 100644
index 2ee819b9..00000000
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LikeLiteralRecord.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2025 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.constraint;
-
-import jakarta.data.metamodel.restrict.Operator;
-
-import java.util.Objects;
-
-record LikeLiteralRecord(String literal, boolean caseSensitive)
-        implements Like {
-
-    public LikeLiteralRecord {
-        Objects.requireNonNull(literal, "Literal must not be null");
-    }
-
-    public LikeLiteralRecord(String literal) {
-        this(literal, true);
-    }
-
-    @Override
-    public String string() {
-        return literal;
-    }
-
-    @Override
-    public Like ignoreCase() {
-        return new LikeLiteralRecord(literal, false);
-    }
-
-    @Override
-    public Character escape() {
-        return null;
-    }
-
-    @Override
-    public boolean pattern() {
-        return false;
-    }
-
-    @Override
-    public Operator operator() {
-        return Operator.EQUAL;
-    }
-
-    @Override
-    public String toString() {
-        return "= '" + literal + "'" + (caseSensitive ? "" : " IGNORE CASE");
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        return obj instanceof LikeLiteralRecord that
-            && literal.equals(that.literal)
-            && caseSensitive == that.caseSensitive;
-    }
-
-    @Override
-    public int hashCode() {
-        return literal.hashCode();
-    }
-}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/LikePatternRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/LikeRecord.java
similarity index 60%
rename from api/src/main/java/jakarta/data/metamodel/constraint/LikePatternRecord.java
rename to api/src/main/java/jakarta/data/metamodel/constraint/LikeRecord.java
index 5645a4ea..83a7c92e 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LikePatternRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/LikeRecord.java
@@ -17,72 +17,55 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 import java.util.Objects;
 
-record LikePatternRecord(String string, boolean caseSensitive, Character escape)
+record LikeRecord(String pattern, Character escape)
         implements Like {
 
     public static final char CHAR_WILDCARD = '_';
     public static final char STRING_WILDCARD = '%';
     public static final char ESCAPE = '\\';
 
-    public LikePatternRecord {
-        Objects.requireNonNull(string, "Pattern must not be null");
-    }
-
-    public LikePatternRecord(String pattern) {
-        this(pattern, true, null);
+    public LikeRecord {
+        Objects.requireNonNull(pattern, "Pattern must not be null");
     }
 
-    public LikePatternRecord(String pattern, Character escape) {
-        this(pattern, true, escape);
+    public LikeRecord(String pattern) {
+        this(pattern, null);
     }
 
-    public LikePatternRecord(String pattern, char charWildcard, char stringWildcard) {
+    public LikeRecord(String pattern, char charWildcard, char stringWildcard) {
         this(pattern, charWildcard, stringWildcard, ESCAPE);
     }
 
-    public LikePatternRecord(String pattern, char charWildcard, char stringWildcard, char escape) {
+    public LikeRecord(String pattern, char charWildcard, char stringWildcard, char escape) {
         this(translate(pattern, charWildcard, stringWildcard, escape), escape);
     }
 
     @Override
-    public LikePatternRecord ignoreCase() {
-        return new LikePatternRecord(string, false, escape);
-    }
-
-    @Override
-    public boolean pattern() {
-        return true;
-    }
-
-    @Override
-    public Operator operator() {
-        return Operator.LIKE;
+    public NotLike negate() {
+        return new NotLikeRecord(pattern, escape);
     }
 
     @Override
     public String toString() {
-        return "LIKE '" + string + "'"
-                + (caseSensitive ? "" : " IGNORE CASE")
+        return "LIKE '" + pattern + "'"
                 + (escape == null ? "" : " ESCAPE '\\'");
     }
 
-    public static LikePatternRecord prefix(String prefix) {
-        return new LikePatternRecord(escape(prefix) + STRING_WILDCARD, ESCAPE);
+    public static LikeRecord prefix(String prefix) {
+        return new LikeRecord(escape(prefix) + STRING_WILDCARD, ESCAPE);
     }
 
-    public static LikePatternRecord suffix(String suffix) {
-        return new LikePatternRecord(STRING_WILDCARD + escape(suffix), ESCAPE);
+    public static LikeRecord suffix(String suffix) {
+        return new LikeRecord(STRING_WILDCARD + escape(suffix), ESCAPE);
     }
 
-    public static LikePatternRecord substring(String substring) {
-        return new LikePatternRecord(STRING_WILDCARD + escape(substring) + STRING_WILDCARD, ESCAPE);
+    public static LikeRecord substring(String substring) {
+        return new LikeRecord(STRING_WILDCARD + escape(substring) + STRING_WILDCARD, ESCAPE);
     }
 
-    private static String escape(String literal) {
+    static String escape(String literal) {
         final var result = new StringBuilder();
         for (int i = 0; i<literal.length(); i++) {
             final char ch = literal.charAt(i);
@@ -94,7 +77,7 @@ record LikePatternRecord(String string, boolean caseSensitive, Character escape)
         return result.toString();
     }
 
-    private static String translate(String pattern, char charWildcard, char stringWildcard, char escape) {
+    static String translate(String pattern, char charWildcard, char stringWildcard, char escape) {
         if ( charWildcard == stringWildcard ) {
             throw new IllegalArgumentException("Cannot use the same character (" + charWildcard + ") for both wildcards.");
         }
@@ -120,14 +103,13 @@ record LikePatternRecord(String string, boolean caseSensitive, Character escape)
 
     @Override
     public boolean equals(Object obj) {
-        return obj instanceof LikePatternRecord that
-            && string.equals(that.string)
-            && caseSensitive == that.caseSensitive
+        return obj instanceof LikeRecord that
+            && pattern.equals(that.pattern)
             && Objects.equals(escape, that.escape);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(string, caseSensitive, escape);
+        return Objects.hash(pattern, escape);
     }
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotBetween.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotBetween.java
new file mode 100644
index 00000000..bad92a9d
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotBetween.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+public interface NotBetween<T extends Comparable<T>> extends Constraint<T> {
+
+    static <T extends Comparable<T>> NotBetween<T> bounds(T lower, T upper) {
+        return new NotBetweenRecord<>(lower, upper);
+    }
+
+    T lowerBound();
+
+    T upperBound();
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotBetweenRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotBetweenRecord.java
new file mode 100644
index 00000000..cb09a87b
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotBetweenRecord.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+import java.util.Objects;
+
+record NotBetweenRecord<T extends Comparable<T>>(T lowerBound, T upperBound)
+    implements NotBetween<T> {
+
+    NotBetweenRecord {
+        Objects.requireNonNull(lowerBound, "lowerBound must not be null");
+        Objects.requireNonNull(upperBound, "upperBound must not be null");
+    }
+
+    @Override
+    public Between<T> negate() {
+        return Between.bounds(lowerBound, upperBound);
+    }
+
+    @Override
+    public String toString() {
+        return lowerBound instanceof String
+                ? "NOT BETWEEN '" + lowerBound + "' AND '" + upperBound + "'"
+                : "NOT BETWEEN " + lowerBound + " AND " + upperBound;
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotEqualTo.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotEqualTo.java
new file mode 100644
index 00000000..37b48435
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotEqualTo.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+public interface NotEqualTo<T> extends Constraint<T> {
+
+    static <T> NotEqualTo<T> value(T value) {
+        return new NotEqualToRecord<>(value);
+    }
+
+    T value();
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotEqualToRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotEqualToRecord.java
new file mode 100644
index 00000000..74e827e0
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotEqualToRecord.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+import java.util.Objects;
+
+record NotEqualToRecord<T>(T value) implements NotEqualTo<T> {
+
+    NotEqualToRecord {
+        Objects.requireNonNull(value, "value must not be null");
+    }
+
+    @Override
+    public EqualTo<T> negate() {
+        return EqualTo.value(value);
+    }
+
+    @Override
+    public String toString() {
+        return value instanceof String ? "<> '" + value + "'" : "<> " + value;
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotIn.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotIn.java
new file mode 100644
index 00000000..b774a145
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotIn.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+import java.util.Set;
+
+public interface NotIn<T> extends Constraint<T> {
+
+    static <T> NotIn<T> values(Set<T> values) {
+        return new NotInRecord<>(values);
+    }
+
+    @SafeVarargs
+    static <T> NotIn<T> values(T... values) {
+        return new NotInRecord<>(Set.of(values));
+    }
+
+    Set<T> values();
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotInRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotInRecord.java
new file mode 100644
index 00000000..27b9b1d2
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotInRecord.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+import java.util.Objects;
+import java.util.Set;
+
+record NotInRecord<T>(Set<T> values) implements NotIn<T> {
+
+    NotInRecord {
+        Objects.requireNonNull(values, "values must not be null");
+        for (T value : values) {
+            Objects.requireNonNull(value, "value must not be null");
+        }
+    }
+
+    @Override
+    public In<T> negate() {
+        return In.values(values);
+    }
+
+    @Override
+    public String toString() {
+        return "NOT IN " + values;
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotLike.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotLike.java
new file mode 100644
index 00000000..ac7c893a
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotLike.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+import static jakarta.data.metamodel.constraint.LikeRecord.ESCAPE;
+import static jakarta.data.metamodel.constraint.LikeRecord.STRING_WILDCARD;
+import static jakarta.data.metamodel.constraint.LikeRecord.translate;
+
+public interface NotLike extends Constraint<String> {
+
+    static NotLike pattern(String pattern) {
+        return new NotLikeRecord(pattern, null);
+    }
+
+    static NotLike pattern(String pattern, char charWildcard, char stringWildcard) {
+        return new NotLikeRecord(translate(pattern, charWildcard, stringWildcard, ESCAPE),
+                                 ESCAPE);
+    }
+
+    static NotLike pattern(String pattern, char charWildcard, char stringWildcard, char escape) {
+        return new NotLikeRecord(translate(pattern, charWildcard, stringWildcard, escape),
+                                 escape);
+    }
+
+    static NotLike prefix(String prefix) {
+        return new NotLikeRecord(LikeRecord.escape(prefix) + STRING_WILDCARD,
+                                 ESCAPE);
+    }
+
+    static NotLike substring(String substring) {
+        return new NotLikeRecord(STRING_WILDCARD + LikeRecord.escape(substring) + STRING_WILDCARD,
+                                 ESCAPE);
+    }
+
+    static NotLike suffix(String suffix) {
+        return new NotLikeRecord(STRING_WILDCARD + LikeRecord.escape(suffix),
+                                 ESCAPE);
+    }
+
+    Character escape();
+
+    String pattern();
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotLikeRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotLikeRecord.java
new file mode 100644
index 00000000..510a1ee6
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotLikeRecord.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+import java.util.Objects;
+
+record NotLikeRecord(String pattern, Character escape) implements NotLike {
+
+    NotLikeRecord {
+        Objects.requireNonNull(pattern, "pattern must not be null");
+    }
+
+    @Override
+    public Like negate() {
+        return new LikeRecord(pattern, escape);
+    }
+
+    @Override
+    public String toString() {
+        return "NOT LIKE '" + pattern + "'" +
+               (escape == null ? "" : " ESCAPE '\\'");
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotNull.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotNull.java
new file mode 100644
index 00000000..5675c8e3
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotNull.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+public interface NotNull<T> extends Constraint<T> {
+
+    @SuppressWarnings("unchecked")
+    static <T> NotNull<T> instance() {
+        return (NotNull<T>) NotNullRecord.INSTANCE;
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotNullRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotNullRecord.java
new file mode 100644
index 00000000..bd9693ee
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotNullRecord.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2025 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.constraint;
+
+record NotNullRecord<T>() implements NotNull<T> {
+    static final NotNullRecord<?> INSTANCE = new NotNullRecord<>();
+
+    @Override
+    public Null<T> negate() {
+        return Null.instance();
+    }
+
+    @Override
+    public String toString() {
+        return "IS NOT NULL";
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/Null.java b/api/src/main/java/jakarta/data/metamodel/constraint/Null.java
index 7f257363..aa2303e8 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/Null.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/Null.java
@@ -18,4 +18,9 @@
 package jakarta.data.metamodel.constraint;
 
 public interface Null<T> extends Constraint<T> {
+
+    @SuppressWarnings("unchecked")
+    static <T> Null<T> instance() {
+        return (Null<T>) NullRecord.INSTANCE;
+    }
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NullRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/NullRecord.java
index 6eb82811..01696910 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/NullRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NullRecord.java
@@ -17,9 +17,9 @@
  */
 package jakarta.data.metamodel.constraint;
 
-import jakarta.data.metamodel.restrict.Operator;
-
 record NullRecord<T>() implements Null<T> {
+    static final NullRecord<?> INSTANCE = new NullRecord<>();
+
     @Override
     public boolean equals(Object obj) {
         return obj instanceof NullRecord;
@@ -29,12 +29,12 @@ record NullRecord<T>() implements Null<T> {
     public int hashCode() {
         return 0;
     }
+
     @Override
-    public Operator operator() {
-        return Operator.NULL;
+    public NotNull<T> negate() {
+        return NotNull.instance();
     }
 
-
     @Override
     public String toString() {
         return "IS NULL";
diff --git a/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestriction.java b/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestriction.java
index d762610e..ba98a383 100644
--- a/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestriction.java
+++ b/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestriction.java
@@ -19,13 +19,11 @@ package jakarta.data.metamodel.restrict;
 
 import jakarta.data.metamodel.constraint.Constraint;
 
-public interface BasicRestriction<T> extends Restriction<T> {
+public interface BasicRestriction<T, V> extends Restriction<T> {
     String attribute();
 
-    Operator comparison();
-
     @Override
-    BasicRestriction<T> negate();
+    BasicRestriction<T, V> negate();
 
-    Constraint<?> constraint();
+    Constraint<V> constraint();
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecord.java b/api/src/main/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecord.java
index 515fd112..3eb70f4c 100644
--- a/api/src/main/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecord.java
@@ -19,6 +19,8 @@ package jakarta.data.metamodel.restrict;
 
 import java.util.List;
 
+import jakarta.data.metamodel.restrict.CompositeRestriction.Type;
+
 // Internal implementation class.
 // The proper way for users to obtain instances is via
 // the Restrict.any(...) or Restrict.all(...) methods
diff --git a/api/src/main/java/jakarta/data/metamodel/restrict/Operator.java b/api/src/main/java/jakarta/data/metamodel/restrict/Operator.java
deleted file mode 100644
index cd11822c..00000000
--- a/api/src/main/java/jakarta/data/metamodel/restrict/Operator.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (c) 2024,2025 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.restrict;
-
-public enum Operator {
-    EQUAL,
-    GREATER_THAN,
-    GREATER_THAN_EQUAL,
-    IN,
-    LESS_THAN,
-    LESS_THAN_EQUAL,
-    LIKE,
-    NOT_EQUAL,
-    NOT_IN,
-    NOT_LIKE,
-    NULL,
-    NOT_NULL,
-    BETWEEN,
-    NOT_BETWEEN;
-
-    /**
-     * Representation of the operator as it appears in query language.
-     * For example, {@link #GREATER_THAN} is represented as {@code >}
-     * in query langugae.
-     *
-     * @return the representation of the operator in query language.
-     */
-    String asQueryLanguage() {
-        return switch (this) {
-            case EQUAL -> "=";
-            case GREATER_THAN -> ">";
-            case GREATER_THAN_EQUAL -> ">=";
-            case IN -> "IN";
-            case LESS_THAN -> "<";
-            case LESS_THAN_EQUAL -> "<=";
-            case LIKE -> "LIKE";
-            case NOT_EQUAL -> "<>";
-            case NOT_IN -> "NOT IN";
-            case NOT_LIKE -> "NOT LIKE";
-            case NULL -> "IS NULL";
-            case NOT_NULL -> "IS NOT NULL";
-            case BETWEEN -> "BETWEEN";
-            case NOT_BETWEEN -> "NOT BETWEEN";
-        };
-    }
-
-    /**
-     * Returns the operator that is the negation of this operator.
-     *
-     * @return the operator that is the negation of this operator.
-     */
-    Operator negate() {
-        return switch (this) {
-            case EQUAL -> NOT_EQUAL;
-            case GREATER_THAN -> LESS_THAN_EQUAL;
-            case GREATER_THAN_EQUAL -> LESS_THAN;
-            case IN -> NOT_IN;
-            case LESS_THAN -> GREATER_THAN_EQUAL;
-            case LESS_THAN_EQUAL -> GREATER_THAN;
-            case LIKE -> NOT_LIKE;
-            case NOT_EQUAL -> EQUAL;
-            case NOT_IN -> IN;
-            case NOT_LIKE -> LIKE;
-            case NULL -> NOT_NULL;
-            case NOT_NULL -> NULL;
-            case BETWEEN -> NOT_BETWEEN;
-            case NOT_BETWEEN -> BETWEEN;
-        };
-    }
-
-    int arity() {
-        return switch (this) {
-            case NULL, NOT_NULL -> 1;
-            case BETWEEN -> 3;
-            default -> 2;
-        };
-    }
-}
diff --git a/api/src/main/java/jakarta/data/metamodel/restrict/Restrict.java b/api/src/main/java/jakarta/data/metamodel/restrict/Restrict.java
index b5d5b7d4..c09e4703 100644
--- a/api/src/main/java/jakarta/data/metamodel/restrict/Restrict.java
+++ b/api/src/main/java/jakarta/data/metamodel/restrict/Restrict.java
@@ -17,12 +17,8 @@
  */
 package jakarta.data.metamodel.restrict;
 
-import jakarta.data.metamodel.constraint.Constraint;
-import jakarta.data.metamodel.constraint.Like;
-
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 
 // TODO document
 // This is one of two places from which to obtain restrictions.
@@ -45,127 +41,12 @@ public class Restrict {
                                                 List.of(restrictions));
     }
 
-    // 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> BasicRestriction<T> restrict(Constraint<?> constraint, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, constraint);
-    }
-
-    public static <T> TextRestriction<T> contains(String substring, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.substring(substring));
-    }
-
-    public static <T> TextRestriction<T> startsWith(String prefix, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.prefix(prefix));
-    }
-
-    public static <T> TextRestriction<T> endsWith(String suffix, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.suffix(suffix));
-    }
-
-    public static <T> BasicRestriction<T> equalTo(Object value, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.equalTo(value));
-    }
-
-    public static <T> TextRestriction<T> equalTo(String value, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.literal(value));
-    }
-
-    public static <T> BasicRestriction<T> isNull(String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.isNull());
-    }
-
-    public static <T, V extends Comparable<V>> BasicRestriction<T> greaterThan(V value, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.greaterThan(value));
-    }
-
-    public static <T, V extends Comparable<V>> BasicRestriction<T> greaterThanEqual(V value, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.greaterThanOrEqual(value));
-    }
-
-    public static <T> BasicRestriction<T> in(Set<?> values, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.in(values));
-    }
-
-    public static <T, V extends Comparable<V>> BasicRestriction<T> lessThan(V value, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.lessThan(value));
-    }
-
-    public static <T, V extends Comparable<V>> BasicRestriction<T> lessThanEqual(V value, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.lessThanOrEqual(value));
-    }
-
-    public static <T, V extends Comparable<V>> BasicRestriction<T> between(V lowerBound, V upperBound, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.between(lowerBound, upperBound));
-    }
-
-    public static <T> TextRestriction<T> like(Like textRange, String attribute) {
-        return new TextRestrictionRecord<>(attribute, textRange);
-    }
-
-    public static <T> TextRestriction<T> like(String pattern, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.pattern(pattern));
-    }
-
-    public static <T> TextRestriction<T> like(String pattern, char charWildcard, char stringWildcard, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.pattern(pattern, charWildcard, stringWildcard));
-    }
-
-    public static <T> TextRestriction<T> like(String pattern, char charWildcard, char stringWildcard, char escape, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.pattern(pattern, charWildcard, stringWildcard, escape));
-    }
-
     // convenience method for those who would prefer to avoid .negate()
     public static <T> Restriction<T> not(Restriction<T> restriction) {
         Objects.requireNonNull(restriction, "Restriction must not be null");
         return restriction.negate();
     }
 
-    public static <T> BasicRestriction<T> notEqualTo(Object value, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.equalTo(value), true);
-    }
-
-    public static <T> TextRestriction<T> notEqualTo(String value, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.literal(value), true);
-    }
-
-    public static <T> BasicRestriction<T> notNull(String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.isNull(), true);
-    }
-
-    public static <T> TextRestriction<T> notContains(String substring, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.substring(substring), true);
-    }
-
-    public static <T> TextRestriction<T> notStartsWith(String prefix, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.prefix(prefix), true);
-    }
-
-    public static <T> TextRestriction<T> notEndsWith(String suffix, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.suffix(suffix), true);
-    }
-
-    public static <T> BasicRestriction<T> notIn(Set<?> values, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.in(values), true);
-    }
-
-    public static <T> TextRestriction<T> notLike(String pattern, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.pattern(pattern), true);
-    }
-
-    public static <T> TextRestriction<T> notLike(String pattern, char charWildcard, char stringWildcard, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.pattern(pattern, charWildcard, stringWildcard), true);
-    }
-
-    public static <T> TextRestriction<T> notLike(String pattern, char charWildcard, char stringWildcard, char escape, String attribute) {
-        return new TextRestrictionRecord<>(attribute, Like.pattern(pattern, charWildcard, stringWildcard, escape), true);
-    }
-
-    public static <T, V extends Comparable<V>> BasicRestriction<T> notBetween(V lowerBound, V upperBound, String attribute) {
-        return new BasicRestrictionRecord<>(attribute, Constraint.between(lowerBound, upperBound), true);
-    }
-
     @SuppressWarnings("unchecked")
     public static <T> Restriction<T> unrestricted() {
         return (Restriction<T>) Unrestricted.INSTANCE;
diff --git a/api/src/main/java/jakarta/data/metamodel/restrict/TextRestriction.java b/api/src/main/java/jakarta/data/metamodel/restrict/TextRestriction.java
index fa684937..0abcdc68 100644
--- a/api/src/main/java/jakarta/data/metamodel/restrict/TextRestriction.java
+++ b/api/src/main/java/jakarta/data/metamodel/restrict/TextRestriction.java
@@ -17,12 +17,11 @@
  */
 package jakarta.data.metamodel.restrict;
 
-import jakarta.data.metamodel.constraint.Like;
+import jakarta.data.metamodel.constraint.Constraint;
 
-public interface TextRestriction<T> extends BasicRestriction<T> {
-    TextRestriction<T> ignoreCase();
+public interface TextRestriction<T> extends BasicRestriction<T, String> {
     @Override
     TextRestriction<T> negate();
     @Override
-    Like constraint();
+    Constraint<String> constraint();
 }


Test patch:
diff --git a/api/src/test/java/jakarta/data/metamodel/AttributeTest.java b/api/src/test/java/jakarta/data/metamodel/AttributeTest.java
index 3882bfc7..c93fd65b 100644
--- a/api/src/test/java/jakarta/data/metamodel/AttributeTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/AttributeTest.java
@@ -18,11 +18,17 @@
 package jakarta.data.metamodel;
 
 import jakarta.data.metamodel.constraint.Constraint;
+import jakarta.data.metamodel.constraint.EqualTo;
+import jakarta.data.metamodel.constraint.In;
+import jakarta.data.metamodel.constraint.NotEqualTo;
+import jakarta.data.metamodel.constraint.NotIn;
+import jakarta.data.metamodel.constraint.NotNull;
+import jakarta.data.metamodel.constraint.Null;
+
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
 
 import jakarta.data.metamodel.restrict.BasicRestriction;
-import jakarta.data.metamodel.restrict.Operator;
 import jakarta.data.metamodel.restrict.Restriction;
 
 import java.util.Set;
@@ -30,44 +36,51 @@ import java.util.Set;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 class AttributeTest {
+    // Mock entity class for tests
+    static class Person {
+        String firstName;
+        String lastName;
+        int ssn;
+        int testAttribute;
+    }
 
-    private final BasicAttribute<String, String> testAttribute = () -> "testAttribute";
+    private final BasicAttribute<Person, String> testAttribute = () -> "testAttribute";
     @Test
     void shouldCreateEqualToRestriction() {
-        Restriction<String> restriction = testAttribute.equalTo("testValue");
+        Restriction<Person> restriction = testAttribute.equalTo("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(basic.constraint()).isInstanceOf(EqualTo.class);
             soft.assertThat(basic.constraint()).isEqualTo(Constraint.equalTo("testValue"));
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.EQUAL);
         });
     }
 
     @Test
     void shouldCreateNotEqualToRestriction() {
-        Restriction<String> restriction = testAttribute.notEqualTo("testValue");
+        Restriction<Person> restriction = testAttribute.notEqualTo("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(basic.constraint()).isEqualTo(Constraint.equalTo("testValue"));
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.NOT_EQUAL);
+            soft.assertThat(basic.constraint()).isInstanceOf(NotEqualTo.class);
+            soft.assertThat(basic.constraint()).isEqualTo(NotEqualTo.value("testValue"));
         });
     }
 
     @Test
     void shouldCreateInRestriction() {
-        Restriction<String> restriction = testAttribute.in(Set.of("value1", "value2"));
+        Restriction<Person> restriction = testAttribute.in(Set.of("value1", "value2"));
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(basic.constraint()).isInstanceOf(In.class);
             soft.assertThat(basic.constraint()).isEqualTo(Constraint.in("value1", "value2"));
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.IN);
         });
     }
 
@@ -80,14 +93,14 @@ class AttributeTest {
 
     @Test
     void shouldCreateNotInRestriction() {
-        Restriction<String> restriction = testAttribute.notIn(Set.of("value1", "value2"));
+        Restriction<Person> restriction = testAttribute.notIn(Set.of("value1", "value2"));
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(basic.constraint()).isEqualTo(Constraint.in("value1", "value2"));
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.NOT_IN);
+            soft.assertThat(basic.constraint()).isInstanceOf(NotIn.class);
+            soft.assertThat(basic.constraint()).isEqualTo(NotIn.values("value1", "value2"));
         });
     }
 
@@ -100,25 +113,25 @@ class AttributeTest {
 
     @Test
     void shouldCreateIsNullRestriction() {
-        Restriction<String> restriction = testAttribute.isNull();
+        Restriction<Person> restriction = testAttribute.isNull();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.NULL);
+            soft.assertThat(basic.constraint()).isInstanceOf(Null.class);
         });
     }
 
     @Test
     void shouldCreateNotNullRestriction() {
-        Restriction<String> restriction = testAttribute.notNull();
+        Restriction<Person> restriction = testAttribute.notNull();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.NOT_NULL);
+            soft.assertThat(basic.constraint()).isInstanceOf(NotNull.class);
         });
     }
 }
diff --git a/api/src/test/java/jakarta/data/metamodel/SortableAttributeTest.java b/api/src/test/java/jakarta/data/metamodel/SortableAttributeTest.java
index c0240325..437981e7 100644
--- a/api/src/test/java/jakarta/data/metamodel/SortableAttributeTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/SortableAttributeTest.java
@@ -18,9 +18,13 @@
 package jakarta.data.metamodel;
 
 import jakarta.data.Sort;
+import jakarta.data.metamodel.constraint.Between;
 import jakarta.data.metamodel.constraint.Constraint;
+import jakarta.data.metamodel.constraint.GreaterThan;
+import jakarta.data.metamodel.constraint.GreaterThanOrEqual;
+import jakarta.data.metamodel.constraint.LessThan;
+import jakarta.data.metamodel.constraint.LessThanOrEqual;
 import jakarta.data.metamodel.restrict.BasicRestriction;
-import jakarta.data.metamodel.restrict.Operator;
 import jakarta.data.metamodel.restrict.Restriction;
 
 import org.assertj.core.api.SoftAssertions;
@@ -28,16 +32,23 @@ import org.junit.jupiter.api.Test;
 
 
 class SortableAttributeTest {
+    // Mock entity class for tests
+    static class Person {
+        String firstName;
+        String lastName;
+        int ssn;
+        int testAttribute;
+    }
 
     //it ignores the implementation of the SortableAttribute interface and uses an anonymous class to test the methods
-    private final ComparableAttribute<String,Integer> testAttribute = new ComparableAttribute<>() {
+    private final ComparableAttribute<Person, Integer> testAttribute = new ComparableAttribute<>() {
         @Override
-        public Sort<String> asc() {
+        public Sort<Person> asc() {
            throw new UnsupportedOperationException("It is not the focus of this test");
         }
 
         @Override
-        public Sort<String> desc() {
+        public Sort<Person> desc() {
             throw new UnsupportedOperationException("It is not the focus of this test");
         }
 
@@ -49,67 +60,67 @@ class SortableAttributeTest {
 
     @Test
     void shouldCreateGreaterThanRestriction() {
-        Restriction<String> restriction = testAttribute.greaterThan(10);
+        Restriction<Person> restriction = testAttribute.greaterThan(10);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, Integer> basic = (BasicRestriction<Person, Integer>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(basic.constraint()).isInstanceOf(GreaterThan.class);
             soft.assertThat(basic.constraint()).isEqualTo(Constraint.greaterThan(10));
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.GREATER_THAN);
         });
     }
 
     @Test
     void shouldCreateGreaterThanEqualRestriction() {
-        Restriction<String> restriction = testAttribute.greaterThanEqual(10);
+        Restriction<Person> restriction = testAttribute.greaterThanEqual(10);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, Integer> basic = (BasicRestriction<Person, Integer>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(basic.constraint()).isInstanceOf(GreaterThanOrEqual.class);
             soft.assertThat(basic.constraint()).isEqualTo(Constraint.greaterThanOrEqual(10));
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.GREATER_THAN_EQUAL);
         });
     }
 
     @Test
     void shouldCreateLessThanRestriction() {
-        Restriction<String> restriction = testAttribute.lessThan(10);
+        Restriction<Person> restriction = testAttribute.lessThan(10);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, Integer> basic = (BasicRestriction<Person, Integer>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(basic.constraint()).isInstanceOf(LessThan.class);
             soft.assertThat(basic.constraint()).isEqualTo(Constraint.lessThan(10));
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.LESS_THAN);
         });
     }
 
     @Test
     void shouldCreateLessThanOrEqualRestriction() {
-        Restriction<String> restriction = testAttribute.lessThanEqual(10);
+        Restriction<Person> restriction = testAttribute.lessThanEqual(10);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> basic = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, Integer> basic = (BasicRestriction<Person, Integer>) restriction;
             soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(basic.constraint()).isInstanceOf(LessThanOrEqual.class);
             soft.assertThat(basic.constraint()).isEqualTo(Constraint.lessThanOrEqual(10));
-            soft.assertThat(basic.comparison()).isEqualTo(Operator.LESS_THAN_EQUAL);
         });
     }
 
     @Test
     void shouldCreateBetweenRestriction() {
-        Restriction<String> restriction = testAttribute.between(5, 15);
+        Restriction<Person> restriction = testAttribute.between(5, 15);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<String> composite = (BasicRestriction<String>) restriction;
+            BasicRestriction<Person, Integer> basic = (BasicRestriction<Person, Integer>) restriction;
 
-            soft.assertThat(composite.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(composite.constraint()).isEqualTo(Constraint.between(5,15));
-            soft.assertThat(composite.comparison()).isEqualTo(Operator.BETWEEN);
+            soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(basic.constraint()).isInstanceOf(Between.class);
+            soft.assertThat(basic.constraint()).isEqualTo(Constraint.between(5,15));
         });
     }
 }
diff --git a/api/src/test/java/jakarta/data/metamodel/TextAttributeTest.java b/api/src/test/java/jakarta/data/metamodel/TextAttributeTest.java
index 8ec76128..82d7579e 100644
--- a/api/src/test/java/jakarta/data/metamodel/TextAttributeTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/TextAttributeTest.java
@@ -18,7 +18,8 @@
 package jakarta.data.metamodel;
 
 import jakarta.data.Sort;
-import jakarta.data.metamodel.restrict.Operator;
+import jakarta.data.metamodel.constraint.Like;
+import jakarta.data.metamodel.constraint.NotLike;
 import jakarta.data.metamodel.restrict.TextRestriction;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
@@ -59,8 +60,8 @@ class TextAttributeTest {
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%testValue%");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
+            soft.assertThat(restriction.constraint()).isInstanceOf(Like.class);
+            soft.assertThat(((Like) restriction.constraint()).pattern()).isEqualTo("%testValue%");
         });
     }
 
@@ -70,8 +71,8 @@ class TextAttributeTest {
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(restriction.constraint().string()).isEqualTo("testValue%");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
+            soft.assertThat(restriction.constraint()).isInstanceOf(Like.class);
+            soft.assertThat(((Like) restriction.constraint()).pattern()).isEqualTo("testValue%");
         });
     }
 
@@ -81,8 +82,8 @@ class TextAttributeTest {
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%testValue");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
+            soft.assertThat(restriction.constraint()).isInstanceOf(Like.class);
+            soft.assertThat(((Like) restriction.constraint()).pattern()).isEqualTo("%testValue");
         });
     }
 
@@ -92,8 +93,8 @@ class TextAttributeTest {
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%test%");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
+            soft.assertThat(restriction.constraint()).isInstanceOf(Like.class);
+            soft.assertThat(((Like) restriction.constraint()).pattern()).isEqualTo("%test%");
         });
     }
 
@@ -103,8 +104,8 @@ class TextAttributeTest {
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%testValue%");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_LIKE);
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotLike.class);
+            soft.assertThat(((NotLike) restriction.constraint()).pattern()).isEqualTo("%testValue%");
         });
     }
 
@@ -114,8 +115,8 @@ class TextAttributeTest {
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%test%");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_LIKE);
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotLike.class);
+            soft.assertThat(((NotLike) restriction.constraint()).pattern()).isEqualTo("%test%");
         });
     }
 
@@ -125,8 +126,8 @@ class TextAttributeTest {
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(restriction.constraint().string()).isEqualTo("testValue%");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_LIKE);
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotLike.class);
+            soft.assertThat(((NotLike) restriction.constraint()).pattern()).isEqualTo("testValue%");
         });
     }
 
@@ -136,8 +137,8 @@ class TextAttributeTest {
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%testValue");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_LIKE);
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotLike.class);
+            soft.assertThat(((NotLike) restriction.constraint()).pattern()).isEqualTo("%testValue");
         });
     }
 }
diff --git a/api/src/test/java/jakarta/data/metamodel/restrict/BasicRestrictionRecordTest.java b/api/src/test/java/jakarta/data/metamodel/restrict/BasicRestrictionRecordTest.java
index 0f3f7791..afdf04b8 100644
--- a/api/src/test/java/jakarta/data/metamodel/restrict/BasicRestrictionRecordTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/restrict/BasicRestrictionRecordTest.java
@@ -17,7 +17,11 @@
  */
 package jakarta.data.metamodel.restrict;
 
+import jakarta.data.metamodel.BasicAttribute;
+import jakarta.data.metamodel.ComparableAttribute;
+import jakarta.data.metamodel.TextAttribute;
 import jakarta.data.metamodel.constraint.*;
+
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
 
@@ -25,77 +29,96 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
 
 
 class BasicRestrictionRecordTest {
+    // A mock static metamodel class for tests
+    interface _Book {
+        String AUTHOR = "author";
+        String ID = "id";
+        String NUMCHAPTERS = "numChapters";
+        String NUMPAGES = "numPages";
+        String TITLE = "title";
+
+        BasicAttribute<Book, String> author = BasicAttribute.of(
+                Book.class, AUTHOR, String.class);
+        TextAttribute<Book> id = TextAttribute.of(
+                Book.class, ID);
+        ComparableAttribute<Book, Integer> numChapters = ComparableAttribute.of(
+                Book.class, NUMCHAPTERS, int.class);
+        ComparableAttribute<Book, Integer> numPages = ComparableAttribute.of(
+                Book.class, NUMPAGES, int.class);
+        BasicAttribute<Book, String> title = BasicAttribute.of(
+                Book.class, TITLE, String.class);
+    }
+
     // A mock entity class for tests
     static class Book {
+        String author;
+        String id;
+        int numChapters;
+        int numPages;
+        String title;
     }
 
     @Test
     void shouldCreateBasicRestrictionWithDefaultNegation() {
-        BasicRestrictionRecord<String> restriction = new BasicRestrictionRecord<>("title", Constraint.equalTo("Java Guide"));
+        BasicRestriction<Book, String> restriction =
+                (BasicRestriction<Book, String>) _Book.title.equalTo("Java Guide");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("title");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.EQUAL);
-            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.equalTo("Java Guide"));
+            soft.assertThat(restriction.constraint()).isEqualTo(EqualTo.value("Java Guide"));
         });
     }
 
     @Test
     void shouldCreateBasicRestrictionWithExplicitNegation() {
-        BasicRestriction<Book> restriction = Restrict.<Book>equalTo("Java Guide", "title")
+        BasicRestriction<Book, String> restriction =
+                (BasicRestriction<Book, String>) _Book.title.equalTo("Java Guide")
                         .negate();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("title");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_EQUAL);
-            soft.assertThat(restriction.constraint()).isEqualTo(Like.literal("Java Guide"));
+            soft.assertThat(restriction.constraint()).isEqualTo(NotEqualTo.value("Java Guide"));
         });
     }
 
     @Test
     void shouldNegateLTERestriction() {
-        BasicRestriction<Book> numChaptersLTE10 = Restrict.lessThanEqual(10, "numChapters");
-        BasicRestriction<Book> numChaptersLTE10Basic = numChaptersLTE10;
-        BasicRestriction<Book> numChaptersGT10Basic = numChaptersLTE10Basic.negate();
+        BasicRestriction<Book, Integer> numChaptersLTE10Basic =
+                (BasicRestriction<Book, Integer>) _Book.numChapters.lessThanEqual(10);
+        BasicRestriction<Book, Integer> numChaptersGT10Basic = numChaptersLTE10Basic.negate();
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(numChaptersLTE10Basic.comparison()).isEqualTo(Operator.LESS_THAN_EQUAL);
-            soft.assertThat(numChaptersLTE10Basic.constraint()).isEqualTo(Constraint.lessThanOrEqual(10));
+            soft.assertThat(numChaptersLTE10Basic.constraint()).isEqualTo(LessThanOrEqual.max(10));
 
-            soft.assertThat(numChaptersGT10Basic.comparison()).isEqualTo(Operator.GREATER_THAN);
-            soft.assertThat(numChaptersGT10Basic.constraint()).isEqualTo(Constraint.lessThanOrEqual(10));
+            soft.assertThat(numChaptersGT10Basic.constraint()).isEqualTo(GreaterThan.bound(10));
         });
     }
 
     @Test
     void shouldNegateNegatedRestriction() {
-        BasicRestriction<Book> titleRestrictionBasic =
-                Restrict.equalTo("A Developer's Guide to Jakarta Data", "title");
-        BasicRestriction<Book> negatedTitleRestrictionBasic = titleRestrictionBasic.negate();
-        BasicRestriction<Book> negatedNegatedTitleRestrictionBasic = negatedTitleRestrictionBasic.negate();
+        BasicRestriction<Book, String> titleRestrictionBasic =
+                (BasicRestriction<Book, String>) _Book.title.equalTo("A Developer's Guide to Jakarta Data");
+        BasicRestriction<Book, String> negatedTitleRestrictionBasic =
+                titleRestrictionBasic.negate();
+        BasicRestriction<Book, String> negatedNegatedTitleRestrictionBasic =
+                negatedTitleRestrictionBasic.negate();
 
-        Constraint<String> expected = Like.literal("A Developer's Guide to Jakarta Data");
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(titleRestrictionBasic.comparison())
-                .isEqualTo(Operator.EQUAL);
             soft.assertThat(titleRestrictionBasic.constraint())
-                .isEqualTo(expected);
+                .isEqualTo(EqualTo.value("A Developer's Guide to Jakarta Data"));
 
-            soft.assertThat(negatedTitleRestrictionBasic.comparison())
-                .isEqualTo(Operator.NOT_EQUAL);
-            soft.assertThat(titleRestrictionBasic.constraint())
-                .isEqualTo(expected);
+            soft.assertThat(negatedNegatedTitleRestrictionBasic.constraint())
+                .isEqualTo(EqualTo.value("A Developer's Guide to Jakarta Data"));
 
-            soft.assertThat(negatedNegatedTitleRestrictionBasic.comparison())
-                .isEqualTo(Operator.EQUAL);
             soft.assertThat(titleRestrictionBasic.constraint())
-                .isEqualTo(expected);
+                .isEqualTo(EqualTo.value("A Developer's Guide to Jakarta Data"));
         });
     }
 
     @Test
     void shouldOutputToString() {
-        Restriction<Book> restriction = Restrict.greaterThan(100, "numPages");
+        Restriction<Book> restriction =
+                (BasicRestriction<Book, Integer>) _Book.numPages.greaterThan(100);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.toString())
@@ -105,26 +128,26 @@ class BasicRestrictionRecordTest {
 
     @Test
     void shouldSupportNegatedRestrictionUsingDefaultConstructor() {
-        BasicRestriction<Book> negatedRestriction = Restrict.notEqualTo((Object) "Unknown", "author");
+        BasicRestriction<Book, String> negatedRestriction =
+                (BasicRestriction<Book, String>) _Book.author.notEqualTo("Unknown");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(negatedRestriction.attribute()).isEqualTo("author");
-            soft.assertThat(negatedRestriction.comparison()).isEqualTo(Operator.NOT_EQUAL);
-            soft.assertThat(negatedRestriction.constraint()).isEqualTo(Constraint.equalTo("Unknown"));
+            soft.assertThat(negatedRestriction.constraint()).isEqualTo(NotEqualTo.value("Unknown"));
         });
     }
 
     @Test
     void shouldThrowExceptionWhenAttributeIsNull() {
-        assertThatThrownBy(() -> new BasicRestrictionRecord<>(null, Constraint.equalTo(("testValue"))))
+        assertThatThrownBy(() -> BasicAttribute.of(Book.class, null, Object.class).equalTo(("testValue")))
                 .isInstanceOf(NullPointerException.class)
-                .hasMessage("Attribute must not be null");
+                .hasMessage("entity attribute name is required");
     }
 
     @Test
     void shouldThrowExceptionWhenValueIsNull() {
-        assertThatThrownBy(() -> new BasicRestrictionRecord<>("title", null))
+        assertThatThrownBy(() -> _Book.title.equalTo(null))
                 .isInstanceOf(NullPointerException.class)
-                .hasMessage("Constraint must not be null");
+                .hasMessage("Value must not be null");
     }
 }
diff --git a/api/src/test/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecordTest.java b/api/src/test/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecordTest.java
index abc9a495..ce81d427 100644
--- a/api/src/test/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecordTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecordTest.java
@@ -17,8 +17,11 @@
  */
 package jakarta.data.metamodel.restrict;
 
+import jakarta.data.metamodel.ComparableAttribute;
+import jakarta.data.metamodel.TextAttribute;
+import jakarta.data.metamodel.impl.ComparableAttributeRecord;
+import jakarta.data.metamodel.impl.TextAttributeRecord;
 
-import jakarta.data.metamodel.constraint.Constraint;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
 
@@ -28,16 +31,40 @@ import java.util.List;
 
 
 class CompositeRestrictionRecordTest {
+    // A mock static metamodel class for tests
+    interface _Author {
+        String AGE = "age";
+        String FIRSTNAME = "firstName";
+        String LASTNAME = "lastName";
+        String NAME = "name";
+        String TITLEOFFIRSTBOOK = "titleOfFirstBook";
+
+        ComparableAttribute<Author, Integer> age = ComparableAttribute.of(
+                Author.class, AGE, int.class);
+        TextAttribute<Author> firstName = TextAttribute.of(
+                Author.class, FIRSTNAME);
+        TextAttribute<Author> lastName = TextAttribute.of(
+                Author.class, LASTNAME);
+        TextAttribute<Author> name = TextAttribute.of(
+                Author.class, NAME);
+        TextAttribute<Author> titleOfFirstBook = TextAttribute.of(
+                Author.class, TITLEOFFIRSTBOOK);
+    }
+
     // A mock entity class for tests
-    static class Person {
+    static class Author {
+        int age;
+        String firstName;
+        String lastName;
+        String name;
     }
 
     @Test
     void shouldCreateCompositeRestrictionWithDefaultNegation() {
-        Restriction<String> restriction1 = new BasicRestrictionRecord<>("title", Constraint.equalTo("Java Guide"));
-        Restriction<String> restriction2 = new BasicRestrictionRecord<>("author", Constraint.equalTo("John Doe"));
+        Restriction<Author> restriction1 = _Author.titleOfFirstBook.equalTo("Java Guide");
+        Restriction<Author> restriction2 = _Author.name.equalTo("John Doe");
 
-        CompositeRestrictionRecord<String> composite = new CompositeRestrictionRecord<>(
+        CompositeRestrictionRecord<Author> composite = new CompositeRestrictionRecord<>(
                 CompositeRestriction.Type.ALL,
                 List.of(restriction1, restriction2)
         );
@@ -51,10 +78,10 @@ class CompositeRestrictionRecordTest {
 
     @Test
     void shouldCreateCompositeRestrictionWithExplicitNegation() {
-        Restriction<String> restriction1 = new BasicRestrictionRecord<>("title", Constraint.equalTo("Java Guide"));
-        Restriction<String> restriction2 = new BasicRestrictionRecord<>("author", Constraint.equalTo("John Doe"));
+        Restriction<Author> restriction1 = _Author.titleOfFirstBook.equalTo("Java Guide");
+        Restriction<Author> restriction2 = _Author.name.equalTo("John Doe");
 
-        CompositeRestrictionRecord<String> composite = new CompositeRestrictionRecord<>(
+        CompositeRestrictionRecord<Author> composite = new CompositeRestrictionRecord<>(
                 CompositeRestriction.Type.ANY,
                 List.of(restriction1, restriction2),
                 true
@@ -76,13 +103,13 @@ class CompositeRestrictionRecordTest {
 
     @Test
     void shouldNegateCompositeRestriction() {
-        Restriction<Person> ageLessThan50 = Restrict.lessThan(50, "age");
-        Restriction<Person> nameStartsWithDuke = Restrict.startsWith("Duke ", "name");
-        CompositeRestriction<Person> all =
-                (CompositeRestriction<Person>) Restrict.all(ageLessThan50, nameStartsWithDuke);
-        CompositeRestriction<Person> allNegated = all.negate();
-        CompositeRestriction<Person> notAll =
-                (CompositeRestriction<Person>) Restrict.not(all);
+        Restriction<Author> ageLessThan50 = _Author.age.lessThan(50);
+        Restriction<Author> nameStartsWithDuke = _Author.name.startsWith("Duke ");
+        CompositeRestriction<Author> all =
+                (CompositeRestriction<Author>) Restrict.all(ageLessThan50, nameStartsWithDuke);
+        CompositeRestriction<Author> allNegated = all.negate();
+        CompositeRestriction<Author> notAll =
+                (CompositeRestriction<Author>) Restrict.not(all);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(all.isNegated()).isEqualTo(false);
@@ -95,13 +122,13 @@ class CompositeRestrictionRecordTest {
 
     @Test
     void shouldNegateNegatedCompositeRestriction() {
-        Restriction<Person> ageBetween20and30 = Restrict.between(20, 30, "age");
-        Restriction<Person> nameContainsDuke = Restrict.contains("Duke", "name");
-        CompositeRestriction<Person> any =
-                (CompositeRestriction<Person>) Restrict.any(ageBetween20and30, nameContainsDuke);
-        CompositeRestriction<Person> anyNegated = any.negate();
-        CompositeRestriction<Person> anyNotNegated =
-                (CompositeRestriction<Person>) Restrict.not(anyNegated);
+        Restriction<Author> ageBetween20and30 = _Author.age.between(20, 30);
+        Restriction<Author> nameContainsDuke = _Author.name.contains("Duke");
+        CompositeRestriction<Author> any =
+                (CompositeRestriction<Author>) Restrict.any(ageBetween20and30, nameContainsDuke);
+        CompositeRestriction<Author> anyNegated = any.negate();
+        CompositeRestriction<Author> anyNotNegated =
+                (CompositeRestriction<Author>) Restrict.not(anyNegated);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(any.isNegated()).isEqualTo(false);
@@ -114,14 +141,14 @@ class CompositeRestrictionRecordTest {
 
     @Test
     void shouldOutputToString() {
-        Restriction<Person> namedJackKarta = Restrict
-                .all(Restrict.equalTo("Jack", "firstName"),
-                     Restrict.equalTo("Karta", "lastName"));
+        Restriction<Author> namedJackKarta = Restrict
+                .all(_Author.firstName.equalTo("Jack"),
+                     _Author.lastName.equalTo("Karta"));
 
-        Restriction<Person> minorOrMissingName = Restrict
-                .any(Restrict.lessThan(18, "age"),
-                     Restrict.equalTo("null", "name"));
-        Restriction<Person> notMinorOrMissingName = minorOrMissingName.negate();
+        Restriction<Author> minorOrMissingName = Restrict
+                .any(_Author.age.lessThan(18),
+                     _Author.name.equalTo("null"));
+        Restriction<Author> notMinorOrMissingName = minorOrMissingName.negate();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(namedJackKarta.toString()).isEqualTo("""
@@ -135,10 +162,10 @@ class CompositeRestrictionRecordTest {
 
     @Test
     void shouldPreserveRestrictionsOrder() {
-        Restriction<String> restriction1 = new BasicRestrictionRecord<>("title", Constraint.equalTo("Java Guide"));
-        Restriction<String> restriction2 = new BasicRestrictionRecord<>("author", Constraint.equalTo("John Doe"));
+        Restriction<Author> restriction1 = _Author.titleOfFirstBook.equalTo("Java Guide");
+        Restriction<Author> restriction2 = _Author.name.equalTo("John Doe");
 
-        CompositeRestrictionRecord<String> composite = new CompositeRestrictionRecord<>(
+        CompositeRestrictionRecord<Author> composite = new CompositeRestrictionRecord<>(
                 CompositeRestriction.Type.ALL,
                 List.of(restriction1, restriction2)
         );
@@ -152,15 +179,15 @@ class CompositeRestrictionRecordTest {
     @Test
     void shouldSupportNegationUsingDefaultConstructor() {
         // Given multiple restrictions
-        Restriction<String> restriction1 = new BasicRestrictionRecord<>("title", Constraint.equalTo("Java Guide"));
-        Restriction<String> restriction2 = new BasicRestrictionRecord<>("author", Constraint.equalTo("John Doe"));
+        Restriction<Author> restriction1 = _Author.titleOfFirstBook.equalTo("Java Guide");
+        Restriction<Author> restriction2 = _Author.name.equalTo("John Doe");
 
         // When creating a composite restriction and manually setting negation
-        CompositeRestrictionRecord<String> composite = new CompositeRestrictionRecord<>(
+        CompositeRestrictionRecord<Author> composite = new CompositeRestrictionRecord<>(
                 CompositeRestriction.Type.ALL,
                 List.of(restriction1, restriction2)
         );
-        CompositeRestrictionRecord<String> negatedComposite = new CompositeRestrictionRecord<>(
+        CompositeRestrictionRecord<Author> negatedComposite = new CompositeRestrictionRecord<>(
                 composite.type(),
                 composite.restrictions(),
                 true
diff --git a/api/src/test/java/jakarta/data/metamodel/restrict/RestrictTest.java b/api/src/test/java/jakarta/data/metamodel/restrict/RestrictTest.java
index 737fae60..5db6e176 100644
--- a/api/src/test/java/jakarta/data/metamodel/restrict/RestrictTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/restrict/RestrictTest.java
@@ -17,13 +17,16 @@
  */
 package jakarta.data.metamodel.restrict;
 
-import jakarta.data.metamodel.constraint.Constraint;
-import jakarta.data.metamodel.constraint.Like;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
 
 import jakarta.data.metamodel.ComparableAttribute;
 import jakarta.data.metamodel.TextAttribute;
+import jakarta.data.metamodel.constraint.Constraint;
+import jakarta.data.metamodel.constraint.EqualTo;
+import jakarta.data.metamodel.constraint.Like;
+import jakarta.data.metamodel.constraint.NotEqualTo;
+import jakarta.data.metamodel.constraint.NotLike;
 
 import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
@@ -66,56 +69,54 @@ class RestrictTest {
 
     @Test
     void shouldCreateEqualToRestriction() {
-        BasicRestriction<Employee> restriction = Restrict.equalTo(0, "attributeName");
+        BasicRestriction<Employee, Integer> restriction =
+                (BasicRestriction<Employee, Integer>) _Employee.yearHired.equalTo(2020);
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.EQUAL);
-            soft.assertThat(restriction.constraint()).isInstanceOf(Constraint.class);
-            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.equalTo(0));
+            soft.assertThat(restriction.attribute()).isEqualTo("yearHired");
+            soft.assertThat(restriction.constraint()).isInstanceOf(EqualTo.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.equalTo(2020));
         });
     }
 
     @Test
     void shouldCreateNotEqualToRestriction() {
-        BasicRestriction<Employee> restriction = Restrict.notEqualTo(0, "attributeName");
+        BasicRestriction<Employee, Integer> restriction =
+                (BasicRestriction<Employee, Integer>) _Employee.badgeNum.notEqualTo(0);
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_EQUAL);
-            soft.assertThat(restriction.constraint()).isInstanceOf(Constraint.class);
-            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.equalTo(0));
+            soft.assertThat(restriction.attribute()).isEqualTo("badgeNum");
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotEqualTo.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(NotEqualTo.value(0));
         });
     }
 
     @Test
     void shouldCreateEqualToStringRestriction() {
-        TextRestriction<Employee> restriction = Restrict.equalTo("value", "attributeName");
+        TextRestriction<Employee> restriction = _Employee.position.equalTo("Software Engineer");
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.EQUAL);
-            soft.assertThat(restriction.constraint()).isInstanceOf(Like.class);
-            soft.assertThat(restriction.constraint()).isEqualTo(Like.literal("value"));
+            soft.assertThat(restriction.attribute()).isEqualTo("position");
+            soft.assertThat(restriction.constraint()).isInstanceOf(EqualTo.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(EqualTo.value("Software Engineer"));
         });
     }
 
     @Test
     void shouldCreateNotEqualToStringRestriction() {
-        TextRestriction<Employee> restriction = Restrict.notEqualTo("value", "attributeName");
+        TextRestriction<Employee> restriction = _Employee.position.notEqualTo("Manager");
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_EQUAL);
-            soft.assertThat(restriction.constraint()).isInstanceOf(Like.class);
-            soft.assertThat(restriction.constraint()).isEqualTo(Like.literal("value"));
+            soft.assertThat(restriction.attribute()).isEqualTo("position");
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotEqualTo.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(NotEqualTo.value("Manager"));
         });
     }
 
     @Test
     void shouldCombineAllRestrictionsWithNegation() {
-        Restriction<Employee> r1 = Restrict.notEqualTo("value1", "attributeName1");
-        Restriction<Employee> r2 = Restrict.greaterThan(100, "attributeName2");
+        Restriction<Employee> r1 = _Employee.name.notEqualTo("Duke");
+        Restriction<Employee> r2 = _Employee.yearHired.greaterThan(2010);
 
         Restriction<Employee> combined = Restrict.all(r1, r2);
 
@@ -131,67 +132,67 @@ class RestrictTest {
 
     @Test
     void shouldCreateContainsRestriction() {
-        TextRestriction<Employee> restriction = Restrict.contains("substring", "attributeName");
+        TextRestriction<Employee> restriction = _Employee.position.contains("Manager");
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%substring%");
+            soft.assertThat(restriction.attribute()).isEqualTo("position");
+            soft.assertThat(restriction.constraint()).isInstanceOf(Like.class);
+            soft.assertThat(((Like) restriction.constraint()).pattern()).isEqualTo("%Manager%");
         });
     }
 
     @Test
     void shouldCreateNegatedContainsRestriction() {
-        TextRestriction<Employee> restriction = Restrict.notContains("substring", "attributeName");
+        TextRestriction<Employee> restriction = _Employee.position.notContains("Director");
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%substring%");
+            soft.assertThat(restriction.attribute()).isEqualTo("position");
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotLike.class);
+            soft.assertThat(((NotLike) restriction.constraint()).pattern()).isEqualTo("%Director%");
         });
     }
 
     @Test
     void shouldCreateStartsWithRestriction() {
-        TextRestriction<Employee> restriction = Restrict.startsWith("prefix", "attributeName");
+        TextRestriction<Employee> restriction = _Employee.position.startsWith("Director");
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("prefix%");
+            soft.assertThat(restriction.attribute()).isEqualTo("position");
+            soft.assertThat(restriction.constraint()).isInstanceOf(Like.class);
+            soft.assertThat(((Like) restriction.constraint()).pattern()).isEqualTo("Director%");
         });
     }
 
     @Test
     void shouldCreateNegatedStartsWithRestriction() {
-        TextRestriction<Employee> restriction = Restrict.notStartsWith("prefix", "attributeName");
+        TextRestriction<Employee> restriction = _Employee.position.notStartsWith("Manager");
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("prefix%");
+            soft.assertThat(restriction.attribute()).isEqualTo("position");
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotLike.class);
+            soft.assertThat(((NotLike) restriction.constraint()).pattern()).isEqualTo("Manager%");
         });
     }
 
     @Test
     void shouldCreateEndsWithRestriction() {
-        TextRestriction<Employee> restriction = Restrict.endsWith("suffix", "attributeName");
+        TextRestriction<Employee> restriction = _Employee.position.endsWith("Manager");
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%suffix");
+            soft.assertThat(restriction.attribute()).isEqualTo("position");
+            soft.assertThat(restriction.constraint()).isInstanceOf(Like.class);
+            soft.assertThat(((Like) restriction.constraint()).pattern()).isEqualTo("%Manager");
         });
     }
 
     @Test
     void shouldCreateNegatedEndsWithRestriction() {
-        TextRestriction<Employee> restriction = Restrict.notEndsWith("suffix", "attributeName");
+        TextRestriction<Employee> restriction = _Employee.position.notEndsWith("Supervisor");
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(restriction.attribute()).isEqualTo("attributeName");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%suffix");
+            soft.assertThat(restriction.attribute()).isEqualTo("position");
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotLike.class);
+            soft.assertThat(((NotLike) restriction.constraint()).pattern()).isEqualTo("%Supervisor");
         });
     }
 
@@ -230,11 +231,25 @@ class RestrictTest {
 
     @Test
     void shouldEscapeToLikePatternCorrectly() {
-        String result = Restrict.endsWith("test_value", "attributeName").constraint().string();
+        Like like = (Like) _Employee.position.endsWith("test_value").constraint();
+        String result = like.pattern();
 
         assertThat(result).isEqualTo("%test\\_value");
     }
 
+    @Test
+    void shouldIgnoreCase() {
+        // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
+        TextRestriction<Employee> restriction = _Employee.position.contains("SOFTWARE");
+        //        .ignoreCase();
+
+        SoftAssertions.assertSoftly(soft -> {
+            soft.assertThat(restriction.attribute()).isEqualTo(_Employee.POSITION);
+            //soft.assertThat(restriction.isCaseSensitive()).isFalse();
+            soft.assertThat(restriction.constraint()).isEqualTo(Like.substring("SOFTWARE"));
+        });
+    }
+
     @Test
     void shouldSupplyUnrestrictedToRepositoryMethod() {
         this.findByPosition("Software Engineer", Restrict.unrestricted());
@@ -242,7 +257,7 @@ class RestrictTest {
 
     @Test
     void shouldThrowExceptionForInvalidWildcard() {
-        assertThatThrownBy(() -> Restrict.like("pattern_value", '_', '_', "attributeName"))
+        assertThatThrownBy(() -> _Employee.name.like("pattern_value", '_', '_'))
                 .isInstanceOf(IllegalArgumentException.class)
                 .hasMessage("Cannot use the same character (_) for both wildcards.");
     }
diff --git a/api/src/test/java/jakarta/data/metamodel/restrict/TextRestrictionRecordTest.java b/api/src/test/java/jakarta/data/metamodel/restrict/TextRestrictionRecordTest.java
index 5051c876..d702480d 100644
--- a/api/src/test/java/jakarta/data/metamodel/restrict/TextRestrictionRecordTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/restrict/TextRestrictionRecordTest.java
@@ -17,182 +17,188 @@
  */
 package jakarta.data.metamodel.restrict;
 
+import jakarta.data.metamodel.TextAttribute;
 import jakarta.data.metamodel.constraint.Like;
+import jakarta.data.metamodel.constraint.NotEqualTo;
+import jakarta.data.metamodel.constraint.NotLike;
+
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
 
-import jakarta.data.metamodel.restrict.BasicRestrictionRecordTest.Book;
-
 import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
 
 
 class TextRestrictionRecordTest {
+    // A mock static metamodel class for tests
+    interface _Book {
+        String AUTHOR = "author";
+        String ID = "id";
+        String TITLE = "title";
+
+        TextAttribute<Book> author = TextAttribute.of(
+                Book.class, AUTHOR);
+        TextAttribute<Book> id = TextAttribute.of(
+                Book.class, ID);
+        TextAttribute<Book> title = TextAttribute.of(
+                Book.class, TITLE);
+    }
+
+    // A mock entity class for tests
+    static class Book {
+        String author;
+        String id;
+        String title;
+    }
 
     @Test
     void shouldCreateTextRestrictionWithDefaultValues() {
-        TextRestrictionRecord<String> restriction = new TextRestrictionRecord<>(
-                "title",
-                Like.pattern("%Java%")
-        );
+        TextRestriction<Book> restriction = _Book.title.like("%Java%");
+
+        Like constraint = (Like) restriction.constraint();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("title");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%Java%");
-            soft.assertThat(restriction.constraint().caseSensitive()).isTrue();
-            soft.assertThat(restriction.constraint().pattern()).isTrue();
-            soft.assertThat(restriction.constraint().escape()).isNull();
+            // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
+            //soft.assertThat(restriction.isCaseSensitive()).isTrue();
+            soft.assertThat(constraint.pattern()).isEqualTo("%Java%");
+            soft.assertThat(constraint.escape()).isNull();
         });
     }
 
     @Test
     void shouldCreateTextRestrictionWithExplicitNegation() {
-        TextRestrictionRecord<String> restriction = new TextRestrictionRecord<>(
-                "title",
-                Like.pattern("%Java%"),
-                true
-        );
+        TextRestriction<Book> restriction = _Book.title.like("%Java%").negate();
+
+        NotLike constraint = (NotLike) restriction.constraint();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("title");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%Java%");
-            soft.assertThat(restriction.constraint().caseSensitive()).isTrue();
-            soft.assertThat(restriction.constraint().pattern()).isTrue();
-            soft.assertThat(restriction.constraint().escape()).isNull();
+            // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
+            //soft.assertThat(restriction.isCaseSensitive()).isTrue();
+            soft.assertThat(constraint.pattern()).isEqualTo("%Java%");
+            soft.assertThat(constraint.escape()).isNull();
         });
     }
 
     @Test
     void shouldIgnoreCaseForTextRestriction() {
-        TextRestrictionRecord<String> caseInsensitiveRestriction = new TextRestrictionRecord<>(
-                "title",
-                Like.pattern("%Java%").ignoreCase()
-        );
+        TextRestriction<Book> restriction = _Book.title.like("%Java%");
+
+        // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
+        //TextRestriction<String> caseInsensitiveRestriction = restriction.ignoreCase();
+        Like constraint = (Like) restriction.constraint();
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(caseInsensitiveRestriction.attribute()).isEqualTo("title");
-            soft.assertThat(caseInsensitiveRestriction.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(caseInsensitiveRestriction.constraint().string()).isEqualTo("%Java%");
-            soft.assertThat(caseInsensitiveRestriction.constraint().caseSensitive()).isFalse();
-            soft.assertThat(caseInsensitiveRestriction.constraint().pattern()).isTrue();
-            soft.assertThat(caseInsensitiveRestriction.constraint().escape()).isNull();
+        //    soft.assertThat(caseInsensitiveRestriction.attribute()).isEqualTo("title");
+        //    soft.assertThat(caseInsensitiveRestriction.isCaseSensitive()).isFalse();
+            soft.assertThat(constraint.pattern()).isEqualTo("%Java%");
+            soft.assertThat(constraint.escape()).isNull();
         });
     }
 
     @Test
     void shouldCreateTextRestrictionWithEscapedValue() {
-        TextRestrictionRecord<String> restriction = new TextRestrictionRecord<>(
-                "title",
-                Like.pattern("%Java%")
-        );
+        TextRestriction<Book> restriction = _Book.title.like("%Java%");
+
+        Like constraint = (Like) restriction.constraint();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("title");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%Java%");
-            soft.assertThat(restriction.constraint().caseSensitive()).isTrue();
-            soft.assertThat(restriction.constraint().pattern()).isTrue();
-            soft.assertThat(restriction.constraint().escape()).isNull();
+            // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
+            //soft.assertThat(restriction.isCaseSensitive()).isTrue();
+            soft.assertThat(constraint.pattern()).isEqualTo("%Java%");
+            soft.assertThat(constraint.escape()).isNull();
         });
     }
 
     @Test
     void shouldCreateTextRestrictionWithCustomWildcards() {
-        TextRestrictionRecord<String> restriction = new TextRestrictionRecord<>(
-                "title",
-                Like.pattern("*Java??", '?', '*', '$')
-        );
+        TextRestriction<Book> restriction = _Book.title.like("*Java??", '?', '*', '$');
+
+        Like constraint = (Like) restriction.constraint();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("title");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("%Java__");
-            soft.assertThat(restriction.constraint().caseSensitive()).isTrue();
-            soft.assertThat(restriction.constraint().pattern()).isTrue();
-            soft.assertThat(restriction.constraint().escape()).isEqualTo('$');
+            // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
+            //soft.assertThat(restriction.isCaseSensitive()).isTrue();
+            soft.assertThat(constraint.pattern()).isEqualTo("%Java__");
+            soft.assertThat(constraint.escape()).isEqualTo('$');
         });
     }
 
     @Test
     void shouldNegateLikeRestriction() {
-        TextRestriction<Book> likeJakartaEE = Restrict.like("%Jakarta EE%", "title");
+        TextRestriction<Book> likeJakartaEE = _Book.title.endsWith("Jakarta EE");
         TextRestriction<Book> notLikeJakartaEE = likeJakartaEE.negate();
-        TextRestriction<Book> anyCaseNotLikeJakartaEE = likeJakartaEE.ignoreCase().negate();
-        TextRestriction<Book> notLikeJakartaEEAnyCase = likeJakartaEE.negate().ignoreCase();
-
-        SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(likeJakartaEE.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(likeJakartaEE.constraint().caseSensitive()).isTrue();
-
-            soft.assertThat(notLikeJakartaEE.comparison()).isEqualTo(Operator.NOT_LIKE);
-            soft.assertThat(notLikeJakartaEE.constraint().caseSensitive()).isTrue();
-
-            soft.assertThat(anyCaseNotLikeJakartaEE.comparison()).isEqualTo(Operator.NOT_LIKE);
-            soft.assertThat(anyCaseNotLikeJakartaEE.constraint().caseSensitive()).isFalse();
-
-            soft.assertThat(notLikeJakartaEEAnyCase.comparison()).isEqualTo(Operator.NOT_LIKE);
-            soft.assertThat(notLikeJakartaEEAnyCase.constraint().caseSensitive()).isFalse();
-        });
+        // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
+        //TextRestriction<Book> anyCaseNotLikeJakartaEE = likeJakartaEE.ignoreCase().negate();
+        //TextRestriction<Book> notLikeJakartaEEAnyCase = likeJakartaEE.negate().ignoreCase();
+
+        //SoftAssertions.assertSoftly(soft -> {
+        //    soft.assertThat(likeJakartaEE.isCaseSensitive()).isTrue();
+        //    soft.assertThat(notLikeJakartaEE.isCaseSensitive()).isTrue();
+        //    soft.assertThat(anyCaseNotLikeJakartaEE.isCaseSensitive()).isFalse();
+        //    soft.assertThat(notLikeJakartaEEAnyCase.isCaseSensitive()).isFalse();
+        //});
     }
 
     @Test
     void shouldNegateNegatedRestriction() {
-        TextRestriction<Book> endsWithJakartaEE = Restrict.endsWith("Jakarta EE", "title");
+        TextRestriction<Book> endsWithJakartaEE = _Book.title.endsWith("Jakarta EE");
         TextRestriction<Book> notEndsWithJakartaEE = endsWithJakartaEE.negate();
         TextRestriction<Book> notNotEndsWithJakartaEE = notEndsWithJakartaEE.negate();
 
         SoftAssertions.assertSoftly(soft -> {
-            soft.assertThat(endsWithJakartaEE.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(endsWithJakartaEE.constraint().string()).isEqualTo("%Jakarta EE");
+            soft.assertThat(endsWithJakartaEE.constraint()).isInstanceOf(Like.class);
+            soft.assertThat(((Like) endsWithJakartaEE.constraint()).pattern())
+                .isEqualTo("%Jakarta EE");
 
-            soft.assertThat(notEndsWithJakartaEE.comparison()).isEqualTo(Operator.NOT_LIKE);
-            soft.assertThat(notEndsWithJakartaEE.constraint().string()).isEqualTo("%Jakarta EE");
+            soft.assertThat(notEndsWithJakartaEE.constraint()).isInstanceOf(NotLike.class);
+            soft.assertThat(((NotLike) notEndsWithJakartaEE.constraint()).pattern())
+                .isEqualTo("%Jakarta EE");
 
-            soft.assertThat(notNotEndsWithJakartaEE.comparison()).isEqualTo(Operator.LIKE);
-            soft.assertThat(notNotEndsWithJakartaEE.constraint().string()).isEqualTo("%Jakarta EE");
+            soft.assertThat(notNotEndsWithJakartaEE.constraint()).isInstanceOf(Like.class);
+            soft.assertThat(((Like) notNotEndsWithJakartaEE.constraint()).pattern())
+                .isEqualTo("%Jakarta EE");
         });
     }
 
     @Test
     void shouldOutputToString() {
-        BasicRestriction<Book> titleRestriction =
-                Restrict.contains("Jakarta Data", "title");
-        BasicRestriction<Book> authorRestriction =
-                Restrict.<Book>equalTo("Myself", "author").ignoreCase().negate();
+        TextRestriction<Book> titleRestriction = _Book.title.contains("Jakarta Data");
+        TextRestriction<Book> authorRestriction = _Book.author.equalTo("Myself")
+                // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
+                //        .ignoreCase()
+                        .negate();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(titleRestriction.toString()).isEqualTo("""
                     title LIKE '%Jakarta Data%' ESCAPE '\\'\
                     """);
-//            soft.assertThat(authorRestriction.toString()).isEqualTo("""
-//                    author <> 'Myself' IGNORE_CASE\
-//                    """);
+            soft.assertThat(authorRestriction.toString()).isEqualTo("""
+                    author <> 'Myself'\
+                    """);
         });
     }
 
     @Test
     void shouldSupportNegationForTextRestriction() {
-        TextRestrictionRecord<String> restriction = new TextRestrictionRecord<>(
-                "author",
-                Like.literal("John Doe"),
-                true
-        );
+        TextRestriction<Book> restriction = _Book.author.equalTo("John Doe").negate();
+
+        NotEqualTo<String> constraint = (NotEqualTo<String>) restriction.constraint();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("author");
-            soft.assertThat(restriction.comparison()).isEqualTo(Operator.NOT_EQUAL);
-            soft.assertThat(restriction.constraint().string()).isEqualTo("John Doe");
-            soft.assertThat(restriction.constraint().caseSensitive()).isTrue();
-            soft.assertThat(restriction.constraint().pattern()).isFalse();
-            soft.assertThat(restriction.constraint().escape()).isNull();
+            // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
+            //soft.assertThat(restriction.isCaseSensitive()).isTrue();
+            soft.assertThat(constraint.value()).isEqualTo("John Doe");
         });
     }
 
     @Test
     void shouldThrowExceptionWhenAttributeIsNullInTextRestriction() {
-        assertThatThrownBy(() -> new TextRestrictionRecord<>(null, Like.literal("testValue")))
+        assertThatThrownBy(() -> TextAttribute.of(_Book.class, null).equalTo("testValue"))
                 .isInstanceOf(NullPointerException.class)
-                .hasMessage("Attribute must not be null");
+                .hasMessage("entity attribute name is required");
     }
 }