Repository: jakartaee/data
Pull request #1021 (2025-03-19 17:19:06)

Expressions
This is a rough straw man for a possible path to take with expressions.

I'm posting this here in an incomplete and uncompilable state, so that you guys can look at the interfaces, before we go and spend any time on adding implementing `record` types.

FTR this turned out a lot different to&mdash;and somewhat more complicated than&mdash;what I perhaps anticipated, but I actually quite like the result. You can write stuff like:

```java
Restriction<Entity> like = _Entity.message.lower().prepend("hello").like("hello %");
Restriction<Entity> notBetween = _Entity.count.abs().times(12L).between(-1L, 1L).negate();
Restriction<Entity> greaterThan = _Entity.decimal.minus(BigDecimal.TEN).satisfies(greaterThan(BigDecimal.ZERO));
```

Note that this is missing the ability to obtain restrictions that compare expressions or different attributes.

Related issue: #459

gavinking: Mainly because I was curious to see what it would look like, I just added a way to do "navigability". We would grow two new interfaces, but it seems to me that they're self-contained, with (surprisingly) no impact elsewhere.

So you could write:

```java
TextRestriction<Book> authorNameStartsWithGavin = 
        _Book.authors.navigate(_Author.name).startsWith("Gavin");
```
gavinking: As a first step on implementation, I've generalized `BasicRestriction` so that it applied to `Expression`s, and not just `BasicAttribute`s. This is pretty clean.
gavinking: I just pushed a strawman implementation of the new operations, based on the idea of a `Function` type which extends `Expression`. I think it's ... actually okay.

Patch:
diff --git a/api/src/main/java/jakarta/data/metamodel/BasicAttribute.java b/api/src/main/java/jakarta/data/metamodel/BasicAttribute.java
index a56c50e7..d3a7f923 100644
--- a/api/src/main/java/jakarta/data/metamodel/BasicAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/BasicAttribute.java
@@ -17,16 +17,9 @@
  */
 package jakarta.data.metamodel;
 
-import jakarta.data.metamodel.constraint.Constraint;
-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 jakarta.data.metamodel.impl.BasicAttributeRecord;
 
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * <p>Represents an entity attribute in the {@link StaticMetamodel}
@@ -35,37 +28,7 @@ import java.util.Set;
  * @param <T> entity class of the static metamodel.
  * @param <V> type of entity attribute (or wrapper type if primitive).
  */
-public interface BasicAttribute<T,V> extends Attribute<T> {
-
-    default Restriction<T> equalTo(V value) {
-        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 new BasicRestrictionRecord<>(name(), In.values(values));
-    }
-
-    default Restriction<T> isNull() {
-        return new BasicRestrictionRecord<>(name(), Null.instance());
-    }
-
-    default Restriction<T> notEqualTo(V value) {
-        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 new BasicRestrictionRecord<>(name(), NotIn.values(values));
-    }
-
-    default Restriction<T> notNull() {
-        return new BasicRestrictionRecord<>(name(), NotNull.instance());
-    }
+public interface BasicAttribute<T,V> extends Attribute<T>, Expression<T,V> {
 
     /**
      * <p>Creates a static metamodel {@code BasicAttribute} representing the
@@ -87,18 +50,5 @@ public interface BasicAttribute<T,V> extends Attribute<T> {
 
         return new BasicAttributeRecord<>(name);
     }
-
-    default Restriction<T> restrict(Constraint<V> constraint) {
-        return new BasicRestrictionRecord<>(name(), constraint);
-    }
 }
 
-/**
- * Hidden internal implementation of BasicAttribute.
- *
- * @param <T> entity class of the static metamodel.
- * @param <V> type of entity attribute (or wrapper type if primitive).
- */
-record BasicAttributeRecord<T,V>(String name)
-    implements BasicAttribute<T,V> {
-}
diff --git a/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java b/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java
index 0143c462..596c487f 100644
--- a/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java
@@ -17,16 +17,9 @@
  */
 package jakarta.data.metamodel;
 
-import java.util.Objects;
+import jakarta.data.metamodel.impl.ComparableAttributeRecord;
 
-import jakarta.data.Sort;
-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;
+import java.util.Objects;
 
 /**
  * <p>Represents a comparable entity attribute in the {@link StaticMetamodel}.
@@ -59,32 +52,8 @@ import jakarta.data.metamodel.restrict.Restriction;
  * @param <T> entity class of the static metamodel.
  * @param <V> type of entity attribute (or wrapper type if primitive).
  */
-public interface ComparableAttribute<T,V extends Comparable<V>>
-        extends BasicAttribute<T,V>, SortableAttribute<T> {
-
-    default Restriction<T> between(V min, V max) {
-        return new BasicRestrictionRecord<>(name(), Between.bounds(min, max));
-    }
-
-    default Restriction<T> notBetween(V min, V max) {
-        return new BasicRestrictionRecord<>(name(), NotBetween.bounds(min, max));
-    }
-
-    default Restriction<T> greaterThan(V value) {
-        return new BasicRestrictionRecord<>(name(), GreaterThan.bound(value));
-    }
-
-    default Restriction<T> greaterThanEqual(V value) {
-        return new BasicRestrictionRecord<>(name(), GreaterThanOrEqual.min(value));
-    }
-
-    default Restriction<T> lessThan(V value) {
-        return new BasicRestrictionRecord<>(name(), LessThan.bound(value));
-    }
-
-    default Restriction<T> lessThanEqual(V value) {
-        return new BasicRestrictionRecord<>(name(), LessThanOrEqual.max(value));
-    }
+public interface ComparableAttribute<T,V extends Comparable<?>>
+        extends BasicAttribute<T,V>, SortableAttribute<T>, ComparableExpression<T,V> {
 
     /**
      * <p>Creates a static metamodel {@code ComparableAttribute} representing the
@@ -97,7 +66,7 @@ public interface ComparableAttribute<T,V extends Comparable<V>>
      * @param attributeType type of the entity attribute.
      * @return instance of {@code ComparableAttribute}.
      */
-    static <T, V extends Comparable<V>> ComparableAttribute<T, V> of(
+    static <T, V extends Comparable<?>> ComparableAttribute<T, V> of(
             Class<T> entityClass,
             String name,
             Class<V> attributeType) {
@@ -109,22 +78,3 @@ public interface ComparableAttribute<T,V extends Comparable<V>>
     }
 }
 
-/**
- * Hidden internal implementation of ComparableAttribute.
- *
- * @param <T> entity class of the static metamodel.
- * @param <V> type of entity attribute (or wrapper type if primitive).
- */
-record ComparableAttributeRecord<T,V extends Comparable<V>>(String name)
-    implements ComparableAttribute<T,V> {
-
-    @Override
-    public Sort<T> asc() {
-        return Sort.asc(name);
-    }
-
-    @Override
-    public Sort<T> desc() {
-        return Sort.desc(name);
-    }
-}
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/metamodel/ComparableExpression.java b/api/src/main/java/jakarta/data/metamodel/ComparableExpression.java
new file mode 100644
index 00000000..e9ce7463
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/ComparableExpression.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+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.BasicRestriction;
+import jakarta.data.metamodel.restrict.Restriction;
+
+public interface ComparableExpression<T,V extends Comparable<?>>
+        extends Expression<T, V> {
+
+    default Restriction<T> between(V min, V max) {
+        return BasicRestriction.of(this, Between.bounds(min, max));
+    }
+
+    default Restriction<T> notBetween(V min, V max) {
+        return BasicRestriction.of(this, NotBetween.bounds(min, max));
+    }
+
+    default Restriction<T> greaterThan(V value) {
+        return BasicRestriction.of(this, GreaterThan.bound(value));
+    }
+
+    default Restriction<T> greaterThanEqual(V value) {
+        return BasicRestriction.of(this, GreaterThanOrEqual.min(value));
+    }
+
+    default Restriction<T> lessThan(V value) {
+        return BasicRestriction.of(this, LessThan.bound(value));
+    }
+
+    default Restriction<T> lessThanEqual(V value) {
+        return BasicRestriction.of(this, LessThanOrEqual.max(value));
+    }
+
+    // Leave for later, since we need a new kind of Restriction for these
+
+//    default Restriction<T> greaterThan(Expression<T,V> value) {
+//        throw new UnsupportedOperationException("not yet implemented");
+//    }
+//
+//    default Restriction<T> greaterThanEqual(Expression<T,V> value) {
+//        throw new UnsupportedOperationException("not yet implemented");
+//    }
+//
+//    default Restriction<T> lessThan(Expression<T,V> value) {
+//        throw new UnsupportedOperationException("not yet implemented");
+//    }
+//
+//    default Restriction<T> lessThanEqual(Expression<T,V> value) {
+//        throw new UnsupportedOperationException("not yet implemented");
+//    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/Expression.java b/api/src/main/java/jakarta/data/metamodel/Expression.java
new file mode 100644
index 00000000..cfe62864
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/Expression.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import jakarta.data.metamodel.constraint.Constraint;
+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.BasicRestriction;
+import jakarta.data.metamodel.restrict.Restriction;
+
+import java.util.Set;
+
+public interface Expression<T,V> {
+
+    default Restriction<T> equalTo(V value) {
+        return BasicRestriction.of(this, Constraint.equalTo(value));
+    }
+
+    default Restriction<T> in(Set<V> values) {
+        if (values == null || values.isEmpty())
+            throw new IllegalArgumentException("values are required");
+
+        return BasicRestriction.of(this, In.values(values));
+    }
+
+    default Restriction<T> isNull() {
+        return BasicRestriction.of(this, Null.instance());
+    }
+
+    default Restriction<T> notEqualTo(V value) {
+        return BasicRestriction.of(this, NotEqualTo.value(value));
+    }
+
+    default Restriction<T> notIn(Set<V> values) {
+        if (values == null || values.isEmpty())
+            throw new IllegalArgumentException("values are required");
+
+        return BasicRestriction.of(this, NotIn.values(values));
+    }
+
+    default Restriction<T> notNull() {
+        return BasicRestriction.of(this, NotNull.instance());
+    }
+
+    // TODO: should this be called restrict() ?
+    default Restriction<T> satisfies(Constraint<V> constraint) {
+        return BasicRestriction.of(this, constraint);
+    }
+
+    // Leave for later, since we need a new kind of Restriction for these
+
+//    default Restriction<T> equalTo(Expression<T,V> expression) {
+//        throw new UnsupportedOperationException("not yet implemented");
+//    }
+//
+//    default Restriction<T> notEqualTo(Expression<T,V> expression) {
+//        throw new UnsupportedOperationException("not yet implemented");
+//    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/NavigableAttribute.java b/api/src/main/java/jakarta/data/metamodel/NavigableAttribute.java
new file mode 100644
index 00000000..a589435d
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/NavigableAttribute.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import jakarta.data.metamodel.impl.NavigableAttributeRecord;
+
+import java.util.Objects;
+
+public interface NavigableAttribute<T,U>
+        extends Attribute<T>, NavigableExpression<T,U> {
+    static <T,U> NavigableAttribute<T,U> of(Class<T> entityClass,
+                                            String name,
+                                            Class<U> attributeType) {
+        Objects.requireNonNull(entityClass, "entity class is required");
+        Objects.requireNonNull(name, "entity attribute name is required");
+        Objects.requireNonNull(attributeType, "entity attribute type is required");
+
+        return new NavigableAttributeRecord<>(name);
+    }
+}
+
diff --git a/api/src/main/java/jakarta/data/metamodel/NavigableExpression.java b/api/src/main/java/jakarta/data/metamodel/NavigableExpression.java
new file mode 100644
index 00000000..c7864d39
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/NavigableExpression.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import jakarta.data.metamodel.path.ComparablePath;
+import jakarta.data.metamodel.path.NavigablePath;
+import jakarta.data.metamodel.path.NumericPath;
+import jakarta.data.metamodel.path.TextPath;
+
+public interface NavigableExpression<T,U> {
+
+    default <V> NavigableExpression<T, V> navigate(NavigableAttribute<U, V> attribute) {
+        return NavigablePath.of(this, attribute);
+    }
+
+    default TextExpression<T> navigate(TextAttribute<U> attribute) {
+        return TextPath.of(this, attribute);
+    }
+
+    default  <C extends Comparable<C>> ComparableExpression<T, C> navigate(ComparableAttribute<U, C> attribute) {
+        return ComparablePath.of(this, attribute);
+    }
+
+    default <N extends Number & Comparable<N>> NumericExpression<T, N> navigate(NumericAttribute<U, N> attribute) {
+        return NumericPath.of(this, attribute);
+    }
+
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/NumericAttribute.java b/api/src/main/java/jakarta/data/metamodel/NumericAttribute.java
new file mode 100644
index 00000000..49a8f122
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/NumericAttribute.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import jakarta.data.metamodel.impl.NumericAttributeRecord;
+
+import java.util.Objects;
+
+public interface NumericAttribute<T,N extends Number & Comparable<N>>
+        extends BasicAttribute<T,N>, NumericExpression<T, N> {
+    static <T,N extends Number & Comparable<N>> NumericAttribute<T,N>
+    of(Class<T> entityClass, String name, Class<N> attributeType) {
+        Objects.requireNonNull(entityClass, "entity class is required");
+        Objects.requireNonNull(name, "entity attribute name is required");
+        Objects.requireNonNull(attributeType, "entity attribute type is required");
+
+        return new NumericAttributeRecord<>(name);
+    }
+}
+
diff --git a/api/src/main/java/jakarta/data/metamodel/NumericExpression.java b/api/src/main/java/jakarta/data/metamodel/NumericExpression.java
new file mode 100644
index 00000000..b3b27283
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/NumericExpression.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+import jakarta.data.metamodel.expression.NumericFunctionExpression;
+import jakarta.data.metamodel.expression.NumericOperatorExpression;
+
+import static jakarta.data.metamodel.expression.NumericFunctionExpression.ABS;
+import static jakarta.data.metamodel.expression.NumericFunctionExpression.NEG;
+import static jakarta.data.metamodel.expression.NumericOperatorExpression.Operator.DIVIDE;
+import static jakarta.data.metamodel.expression.NumericOperatorExpression.Operator.MINUS;
+import static jakarta.data.metamodel.expression.NumericOperatorExpression.Operator.PLUS;
+import static jakarta.data.metamodel.expression.NumericOperatorExpression.Operator.TIMES;
+
+public interface NumericExpression<T, N extends Number & Comparable<N>>
+        extends ComparableExpression<T, N> {
+
+    default NumericExpression<T,N> abs() {
+        return NumericFunctionExpression.of(ABS, this);
+    }
+    default NumericExpression<T,N> negated() {
+        return NumericFunctionExpression.of(NEG, this);
+    }
+
+    default NumericExpression<T,N> plus(N other) {
+        return NumericOperatorExpression.of(PLUS, this, other);
+    }
+    default NumericExpression<T,N> minus(N other) {
+        return NumericOperatorExpression.of(MINUS, this, other);
+    }
+    default NumericExpression<T,N> times(N other) {
+        return NumericOperatorExpression.of(TIMES, this, other);
+    }
+    default NumericExpression<T,N> divide(N other) {
+        return NumericOperatorExpression.of(DIVIDE, this, other);
+    }
+
+    default NumericExpression<T,N> plus(NumericExpression<T,N> other) {
+        return NumericOperatorExpression.of(PLUS, this, other);
+    }
+    default NumericExpression<T,N> minus(NumericExpression<T,N> other) {
+        return NumericOperatorExpression.of(MINUS, this, other);
+    }
+    default NumericExpression<T,N> times(NumericExpression<T,N> other) {
+        return NumericOperatorExpression.of(TIMES, this, other);
+    }
+    default NumericExpression<T,N> divide(NumericExpression<T,N> other) {
+        return NumericOperatorExpression.of(DIVIDE, this, other);
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/Path.java b/api/src/main/java/jakarta/data/metamodel/Path.java
new file mode 100644
index 00000000..d7a185a6
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/Path.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface Path<T,U> {
+    NavigableExpression<T,U> expression();
+    Attribute<U> attribute();
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java b/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java
index 3f08fc47..e14576f7 100644
--- a/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java
@@ -43,14 +43,18 @@ public interface SortableAttribute<T> extends Attribute<T> {
      *
      * @return a request for an ascending sort on the entity attribute.
      */
-    Sort<T> asc();
+    default Sort<T> asc() {
+        return Sort.asc(name());
+    }
 
     /**
      * Obtain a request for a descending {@link Sort} based on the entity attribute.
      *
      * @return a request for a descending sort on the entity attribute.
      */
-    Sort<T> desc();
+    default Sort<T> desc() {
+        return Sort.desc(name());
+    }
 
     /**
      * <p>Creates a static metamodel {@code SortableAttribute} representing the
diff --git a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
index 8f6f1485..ec266e68 100644
--- a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
@@ -17,130 +17,34 @@
  */
 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.TextRestriction;
+
+import java.util.Objects;
 
 /**
  * Represents an textual entity attribute in the {@link StaticMetamodel}.
  *
  * @param <T> entity class of the static metamodel.
  */
-public interface TextAttribute<T> extends ComparableAttribute<T,String> {
+public interface TextAttribute<T> extends ComparableAttribute<T,String>, TextExpression<T> {
 
     /**
      * Obtain a request for an ascending, case-insensitive {@link Sort} based on the entity attribute.
      *
      * @return a request for an ascending, case-insensitive sort on the entity attribute.
      */
-    Sort<T> ascIgnoreCase();
+    default Sort<T> ascIgnoreCase() {
+        return Sort.ascIgnoreCase(name());
+    }
 
     /**
      * Obtain a request for a descending, case insensitive {@link Sort} based on the entity attribute.
      *
      * @return a request for a descending, case insensitive sort on the entity attribute.
      */
-    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 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 new TextRestrictionRecord<>(name(), NotEqualTo.value(value));
-    }
-
-    default TextRestriction<T> like(Like pattern) {
-        return new TextRestrictionRecord<>(name(), pattern);
-
-    }
-
-    default TextRestriction<T> like(String pattern) {
-        return new TextRestrictionRecord<>(name(), Like.pattern(pattern));
-    }
-
-    default TextRestriction<T> like(String pattern, char charWildcard, char stringWildcard) {
-        Like constraint = Like.pattern(pattern, charWildcard, stringWildcard);
-        return new TextRestrictionRecord<>(name(), constraint);
-    }
-
-    default TextRestriction<T> like(String pattern, char charWildcard, char stringWildcard, char escape) {
-        Like constraint = Like.pattern(pattern, charWildcard, stringWildcard, escape);
-        return new TextRestrictionRecord<>(name(), constraint);
-    }
-
-    default TextRestriction<T> notLike(String pattern) {
-        return new TextRestrictionRecord<>(name(), NotLike.pattern(pattern));
-    }
-
-    default TextRestriction<T> notLike(String pattern, char charWildcard, char stringWildcard) {
-        NotLike constraint = NotLike.pattern(pattern, charWildcard, stringWildcard);
-        return new TextRestrictionRecord<>(name(), constraint);
-    }
-
-    default TextRestriction<T> notLike(String pattern, char charWildcard, char stringWildcard, char escape) {
-        NotLike constraint = NotLike.pattern(pattern, charWildcard, stringWildcard, escape);
-        return new TextRestrictionRecord<>(name(), constraint);
-    }
-
-    default TextRestriction<T> contains(String substring) {
-        return new TextRestrictionRecord<>(name(), Like.substring(substring));
-    }
-
-    default TextRestriction<T> notContains(String substring) {
-        return new TextRestrictionRecord<>(name(), NotLike.substring(substring));
-    }
-
-    default TextRestriction<T> endsWith(String suffix) {
-        return new TextRestrictionRecord<>(name(), Like.suffix(suffix));
-    }
-
-    default TextRestriction<T> notEndsWith(String suffix) {
-        return new TextRestrictionRecord<>(name(), NotLike.suffix(suffix));
+    default Sort<T> descIgnoreCase() {
+        return Sort.descIgnoreCase(name());
     }
 
     /**
@@ -159,12 +63,4 @@ public interface TextAttribute<T> extends ComparableAttribute<T,String> {
         return new TextAttributeRecord<>(name);
     }
 
-    default TextRestriction<T> startsWith(String prefix) {
-        return new TextRestrictionRecord<>(name(), Like.prefix(prefix));
-    }
-
-    default TextRestriction<T> notStartsWith(String prefix) {
-        return new TextRestrictionRecord<>(name(), NotLike.prefix(prefix));
-    }
-
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/TextExpression.java b/api/src/main/java/jakarta/data/metamodel/TextExpression.java
new file mode 100644
index 00000000..281cad10
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/TextExpression.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+import jakarta.data.metamodel.constraint.Like;
+import jakarta.data.metamodel.constraint.NotLike;
+import jakarta.data.metamodel.expression.NumericFunctionExpression;
+import jakarta.data.metamodel.expression.TextFunctionExpression;
+import jakarta.data.metamodel.restrict.BasicRestriction;
+import jakarta.data.metamodel.restrict.Restriction;
+
+import static jakarta.data.metamodel.expression.NumericFunctionExpression.LENGTH;
+import static jakarta.data.metamodel.expression.TextFunctionExpression.CONCAT;
+import static jakarta.data.metamodel.expression.TextFunctionExpression.LEFT;
+import static jakarta.data.metamodel.expression.TextFunctionExpression.LOWER;
+import static jakarta.data.metamodel.expression.TextFunctionExpression.RIGHT;
+import static jakarta.data.metamodel.expression.TextFunctionExpression.UPPER;
+
+public interface TextExpression<T> extends ComparableExpression<T,String> {
+
+    default TextExpression<T> prepend(String string) {
+        return TextFunctionExpression.of(CONCAT, string, this);
+    }
+    default TextExpression<T> append(String string) {
+        return TextFunctionExpression.of(CONCAT, this, string);
+    }
+
+    default TextExpression<T> prepend(TextExpression<T> string) {
+        return TextFunctionExpression.of(CONCAT, string, this);
+    }
+    default TextExpression<T> append(TextExpression<T> string) {
+        return TextFunctionExpression.of(CONCAT, string, this);
+    }
+
+    default TextExpression<T> upper() {
+        return TextFunctionExpression.of(UPPER, this);
+    }
+    default TextExpression<T> lower() {
+        return TextFunctionExpression.of(LOWER, this);
+    }
+
+    default TextExpression<T> left(int length) {
+        return TextFunctionExpression.of(LEFT, this, length);
+    }
+    default TextExpression<T> right(int length) {
+        return TextFunctionExpression.of(RIGHT, this, length);
+    }
+
+    default NumericExpression<T,Integer> length() {
+        return NumericFunctionExpression.of(LENGTH, this);
+    }
+
+    default Restriction<T> like(Like pattern) {
+        return BasicRestriction.of(this, pattern);
+    }
+
+    default Restriction<T> like(String pattern) {
+        return BasicRestriction.of(this, Like.pattern(pattern));
+    }
+
+    default Restriction<T> like(String pattern, char charWildcard, char stringWildcard) {
+        Like constraint = Like.pattern(pattern, charWildcard, stringWildcard);
+        return BasicRestriction.of(this, constraint);
+    }
+
+    default Restriction<T> like(String pattern, char charWildcard, char stringWildcard, char escape) {
+        Like constraint = Like.pattern(pattern, charWildcard, stringWildcard, escape);
+        return BasicRestriction.of(this, constraint);
+    }
+
+    default Restriction<T> notLike(String pattern) {
+        return BasicRestriction.of(this, NotLike.pattern(pattern));
+    }
+
+    default Restriction<T> notLike(String pattern, char charWildcard, char stringWildcard) {
+        NotLike constraint = NotLike.pattern(pattern, charWildcard, stringWildcard);
+        return BasicRestriction.of(this, constraint);
+    }
+
+    default Restriction<T> notLike(String pattern, char charWildcard, char stringWildcard, char escape) {
+        NotLike constraint = NotLike.pattern(pattern, charWildcard, stringWildcard, escape);
+        return BasicRestriction.of(this, constraint);
+    }
+
+    default Restriction<T> contains(String substring) {
+        return BasicRestriction.of(this, Like.substring(substring));
+    }
+
+    default Restriction<T> notContains(String substring) {
+        return BasicRestriction.of(this, NotLike.substring(substring));
+    }
+
+    default Restriction<T> startsWith(String prefix) {
+        return BasicRestriction.of(this, Like.prefix(prefix));
+    }
+
+    default Restriction<T> notStartsWith(String prefix) {
+        return BasicRestriction.of(this, NotLike.prefix(prefix));
+    }
+
+    default Restriction<T> endsWith(String suffix) {
+        return BasicRestriction.of(this, Like.suffix(suffix));
+    }
+
+    default Restriction<T> notEndsWith(String suffix) {
+        return BasicRestriction.of(this, NotLike.suffix(suffix));
+    }
+
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/TextRestrictionRecord.java b/api/src/main/java/jakarta/data/metamodel/TextRestrictionRecord.java
deleted file mode 100644
index 1f010b8c..00000000
--- a/api/src/main/java/jakarta/data/metamodel/TextRestrictionRecord.java
+++ /dev/null
@@ -1,49 +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;
-
-import jakarta.data.metamodel.constraint.Constraint;
-import jakarta.data.metamodel.restrict.TextRestriction;
-
-import java.util.Objects;
-
-record TextRestrictionRecord<T>(String attribute, Constraint<String> constraint)
-        implements TextRestriction<T> {
-
-    TextRestrictionRecord {
-        Objects.requireNonNull(attribute, "Attribute must not be null");
-        Objects.requireNonNull(constraint, "Constraint must not be null");
-    }
-
-    @Override
-    public TextRestrictionRecord<T> negate() {
-        return new TextRestrictionRecord<>(attribute, constraint.negate());
-    }
-
-    /**
-     * Textual representation of a text restriction.
-     * For example,
-     * <pre>title LIKE '%JAKARTA EE%'</pre>
-     *
-     * @return textual representation of a text restriction.
-     */
-    @Override
-    public String toString() {
-        return attribute + ' ' + constraint;
-    }
-}
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 3c6750f7..cff706c1 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/Between.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/Between.java
@@ -17,9 +17,9 @@
  */
 package jakarta.data.metamodel.constraint;
 
-public interface Between<T extends Comparable<T>> extends Constraint<T> {
+public interface Between<T extends Comparable<?>> extends Constraint<T> {
 
-    static <T extends Comparable<T>> Between<T> bounds(T lower, T upper) {
+    static <T extends Comparable<?>> Between<T> bounds(T lower, T upper) {
         return new BetweenRecord<>(lower, upper);
     }
 
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 729149f2..c7b43374 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/BetweenRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/BetweenRecord.java
@@ -19,7 +19,7 @@ package jakarta.data.metamodel.constraint;
 
 import java.util.Objects;
 
-record BetweenRecord<T extends Comparable<T>>(T lowerBound, T upperBound)
+record BetweenRecord<T extends Comparable<?>>(T lowerBound, T upperBound)
         implements Between<T> {
     public BetweenRecord {
         Objects.requireNonNull(lowerBound);
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 5487d6c4..bc19008b 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/Constraint.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/Constraint.java
@@ -38,29 +38,29 @@ public interface Constraint<T> {
         return new NullRecord<>();
     }
 
-    static <T extends Comparable<T>> Constraint<T> greaterThan(T bound) {
+    static <T extends Comparable<?>> Constraint<T> greaterThan(T bound) {
         return new GreaterThanRecord<>(bound);
     }
 
-    static <T extends Comparable<T>> Constraint<T> lessThan(T bound) {
+    static <T extends Comparable<?>> Constraint<T> lessThan(T bound) {
         return new LessThanRecord<>(bound);
     }
 
-    static <T extends Comparable<T>> Constraint<T> greaterThanOrEqual(T bound) {
+    static <T extends Comparable<?>> Constraint<T> greaterThanOrEqual(T bound) {
         return new GreaterThanOrEqualRecord<>(bound);
     }
 
-    static <T extends Comparable<T>> Constraint<T> lessThanOrEqual(T bound) {
+    static <T extends Comparable<?>> Constraint<T> lessThanOrEqual(T bound) {
         return new LessThanOrEqualRecord<>(bound);
     }
 
-    static <T extends Comparable<T>> Constraint<T> between(T lowerBound, T upperBound) {
+    static <T extends Comparable<?>> Constraint<T> between(T lowerBound, T upperBound) {
         return new BetweenRecord<>(lowerBound, upperBound);
     }
 
     Constraint<T> negate();
 
-    static <T extends Comparable<T>> Constraint<T> notBetween(T lowerBound, T upperBound) {
+    static <T extends Comparable<?>> Constraint<T> notBetween(T lowerBound, T upperBound) {
         return NotBetween.bounds(lowerBound, upperBound);
     }
 
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 5701bb5a..c30bc78b 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThan.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThan.java
@@ -17,9 +17,9 @@
  */
 package jakarta.data.metamodel.constraint;
 
-public interface GreaterThan<T extends Comparable<T>> extends Constraint<T> {
+public interface GreaterThan<T extends Comparable<?>> extends Constraint<T> {
 
-    static <T extends Comparable<T>> GreaterThan<T> bound(T lowerBound) {
+    static <T extends Comparable<?>> GreaterThan<T> bound(T lowerBound) {
         return new GreaterThanRecord<>(lowerBound);
     }
 
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 c4a15732..a3d0dc84 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqual.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqual.java
@@ -17,9 +17,9 @@
  */
 package jakarta.data.metamodel.constraint;
 
-public interface GreaterThanOrEqual<T extends Comparable<T>> extends Constraint<T> {
+public interface GreaterThanOrEqual<T extends Comparable<?>> extends Constraint<T> {
 
-    static <T extends Comparable<T>> GreaterThanOrEqual<T> min(T minimum) {
+    static <T extends Comparable<?>> GreaterThanOrEqual<T> min(T minimum) {
         return new GreaterThanOrEqualRecord<>(minimum);
     }
 
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 010b384c..fdbb8ce7 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqualRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanOrEqualRecord.java
@@ -19,7 +19,7 @@ package jakarta.data.metamodel.constraint;
 
 import java.util.Objects;
 
-record GreaterThanOrEqualRecord<T extends Comparable<T>>(T bound)
+record GreaterThanOrEqualRecord<T extends Comparable<?>>(T bound)
         implements GreaterThanOrEqual<T> {
     public GreaterThanOrEqualRecord {
         Objects.requireNonNull(bound, "Lower bound must not be null");
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 e44fd1a4..55b57c77 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/GreaterThanRecord.java
@@ -19,7 +19,7 @@ package jakarta.data.metamodel.constraint;
 
 import java.util.Objects;
 
-record GreaterThanRecord<T extends Comparable<T>>(T bound)
+record GreaterThanRecord<T extends Comparable<?>>(T bound)
         implements GreaterThan<T> {
     public GreaterThanRecord {
         Objects.requireNonNull(bound, "Lower bound must not be null");
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 13f7ce73..a08f3f70 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LessThan.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/LessThan.java
@@ -17,9 +17,9 @@
  */
 package jakarta.data.metamodel.constraint;
 
-public interface LessThan<T extends Comparable<T>> extends Constraint<T> {
+public interface LessThan<T extends Comparable<?>> extends Constraint<T> {
 
-    static <T extends Comparable<T>> LessThan<T> bound(T upperBound) {
+    static <T extends Comparable<?>> LessThan<T> bound(T upperBound) {
         return new LessThanRecord<>(upperBound);
     }
 
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 db6886ce..8dccc0f5 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqual.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqual.java
@@ -17,9 +17,9 @@
  */
 package jakarta.data.metamodel.constraint;
 
-public interface LessThanOrEqual<T extends Comparable<T>> extends Constraint<T> {
+public interface LessThanOrEqual<T extends Comparable<?>> extends Constraint<T> {
 
-    static <T extends Comparable<T>> LessThanOrEqual<T> max(T maximum) {
+    static <T extends Comparable<?>> LessThanOrEqual<T> max(T maximum) {
         return new LessThanOrEqualRecord<>(maximum);
     }
 
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 05df0c7d..3cbbb182 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqualRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/LessThanOrEqualRecord.java
@@ -19,7 +19,7 @@ package jakarta.data.metamodel.constraint;
 
 import java.util.Objects;
 
-record LessThanOrEqualRecord<T extends Comparable<T>>(T bound)
+record LessThanOrEqualRecord<T extends Comparable<?>>(T bound)
         implements LessThanOrEqual<T> {
     public LessThanOrEqualRecord {
         Objects.requireNonNull(bound, "Upper bound must not be null");
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 7a3058bf..accbb215 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/LessThanRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/LessThanRecord.java
@@ -19,7 +19,7 @@ package jakarta.data.metamodel.constraint;
 
 import java.util.Objects;
 
-record LessThanRecord<T extends Comparable<T>>(T bound)
+record LessThanRecord<T extends Comparable<?>>(T bound)
         implements LessThan<T> {
     public LessThanRecord {
         Objects.requireNonNull(bound, "Upper bound must not be null");
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotBetween.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotBetween.java
index bad92a9d..e1780403 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/NotBetween.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotBetween.java
@@ -17,9 +17,9 @@
  */
 package jakarta.data.metamodel.constraint;
 
-public interface NotBetween<T extends Comparable<T>> extends Constraint<T> {
+public interface NotBetween<T extends Comparable<?>> extends Constraint<T> {
 
-    static <T extends Comparable<T>> NotBetween<T> bounds(T lower, T upper) {
+    static <T extends Comparable<?>> NotBetween<T> bounds(T lower, T upper) {
         return new NotBetweenRecord<>(lower, upper);
     }
 
diff --git a/api/src/main/java/jakarta/data/metamodel/constraint/NotBetweenRecord.java b/api/src/main/java/jakarta/data/metamodel/constraint/NotBetweenRecord.java
index cb09a87b..0b0b24b0 100644
--- a/api/src/main/java/jakarta/data/metamodel/constraint/NotBetweenRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/constraint/NotBetweenRecord.java
@@ -19,7 +19,7 @@ package jakarta.data.metamodel.constraint;
 
 import java.util.Objects;
 
-record NotBetweenRecord<T extends Comparable<T>>(T lowerBound, T upperBound)
+record NotBetweenRecord<T extends Comparable<?>>(T lowerBound, T upperBound)
     implements NotBetween<T> {
 
     NotBetweenRecord {
diff --git a/api/src/main/java/jakarta/data/metamodel/restrict/TextRestriction.java b/api/src/main/java/jakarta/data/metamodel/expression/FunctionExpression.java
similarity index 71%
rename from api/src/main/java/jakarta/data/metamodel/restrict/TextRestriction.java
rename to api/src/main/java/jakarta/data/metamodel/expression/FunctionExpression.java
index 0abcdc68..fa4025cb 100644
--- a/api/src/main/java/jakarta/data/metamodel/restrict/TextRestriction.java
+++ b/api/src/main/java/jakarta/data/metamodel/expression/FunctionExpression.java
@@ -15,13 +15,13 @@
  *
  * SPDX-License-Identifier: Apache-2.0
  */
-package jakarta.data.metamodel.restrict;
+package jakarta.data.metamodel.expression;
 
-import jakarta.data.metamodel.constraint.Constraint;
+import jakarta.data.metamodel.Expression;
 
-public interface TextRestriction<T> extends BasicRestriction<T, String> {
-    @Override
-    TextRestriction<T> negate();
-    @Override
-    Constraint<String> constraint();
+import java.util.List;
+
+public interface FunctionExpression<T,V> extends Expression<T,V> {
+    String name();
+    List<? extends Expression<T,?>> arguments();
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/NumericFunctionExpression.java b/api/src/main/java/jakarta/data/metamodel/expression/NumericFunctionExpression.java
new file mode 100644
index 00000000..d7052ec2
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/NumericFunctionExpression.java
@@ -0,0 +1,40 @@
+/*
+ * 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.expression;
+
+import jakarta.data.metamodel.NumericExpression;
+import jakarta.data.metamodel.TextExpression;
+
+import java.util.List;
+
+public interface NumericFunctionExpression<T, N extends Number & Comparable<N>>
+        extends FunctionExpression<T,N>, NumericExpression<T,N> {
+
+    String ABS = "abs";
+    String NEG = "neg";
+    String LENGTH = "length";
+
+    static <T,N extends Number & Comparable<N>> NumericFunctionExpression<T, N>
+    of(String name, TextExpression<T> argument) {
+        return new NumericFunctionExpressionRecord<>(name, List.of(argument));
+    }
+    static <T,N extends Number & Comparable<N>> NumericFunctionExpression<T, N>
+    of(String name, NumericExpression<T,N> argument) {
+        return new NumericFunctionExpressionRecord<>(name, List.of(argument));
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/NumericFunctionExpressionRecord.java b/api/src/main/java/jakarta/data/metamodel/expression/NumericFunctionExpressionRecord.java
new file mode 100644
index 00000000..8160d4b4
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/NumericFunctionExpressionRecord.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.expression;
+
+import jakarta.data.metamodel.Expression;
+
+import java.util.List;
+
+record NumericFunctionExpressionRecord<T, N extends Number & Comparable<N>>
+        (String name, List<Expression<T,?>> arguments)
+        implements NumericFunctionExpression<T,N> {
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/NumericLiteral.java b/api/src/main/java/jakarta/data/metamodel/expression/NumericLiteral.java
new file mode 100644
index 00000000..ec3ca65b
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/NumericLiteral.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.expression;
+
+import jakarta.data.metamodel.NumericExpression;
+
+public interface NumericLiteral<T, N extends Number & Comparable<N>>
+        extends NumericExpression<T,N> {
+    N value();
+
+    static <T, N extends Number & Comparable<N>> NumericLiteral<T, N> of(N value) {
+        return new NumericLiteralRecord<>(value);
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/NumericLiteralRecord.java b/api/src/main/java/jakarta/data/metamodel/expression/NumericLiteralRecord.java
new file mode 100644
index 00000000..b498f64d
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/NumericLiteralRecord.java
@@ -0,0 +1,22 @@
+/*
+ * 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.expression;
+
+record NumericLiteralRecord<T, N extends Number & Comparable<N>>
+        (N value) implements NumericLiteral<T,N> {
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/NumericOperatorExpression.java b/api/src/main/java/jakarta/data/metamodel/expression/NumericOperatorExpression.java
new file mode 100644
index 00000000..57fca965
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/NumericOperatorExpression.java
@@ -0,0 +1,40 @@
+/*
+ * 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.expression;
+
+import jakarta.data.metamodel.Expression;
+import jakarta.data.metamodel.NumericExpression;
+
+public interface NumericOperatorExpression<T, N extends Number & Comparable<N>>
+        extends NumericExpression<T,N> {
+    enum Operator {
+        PLUS, MINUS, TIMES, DIVIDE
+    }
+    Operator operator();
+    Expression<T,N> left();
+    Expression<T,N> right();
+
+    static <T, N extends Number & Comparable<N>>
+    NumericOperatorExpression<T,N> of(Operator operator, NumericExpression<T, N> left, N right) {
+        return new NumericOperatorExpressionRecord<>(operator, left, NumericLiteral.of(right));
+    }
+    static <T, N extends Number & Comparable<N>>
+    NumericOperatorExpression<T,N> of(Operator operator, NumericExpression<T, N> left, NumericExpression<T, N> right) {
+        return new NumericOperatorExpressionRecord<>(operator, left, right);
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/NumericOperatorExpressionRecord.java b/api/src/main/java/jakarta/data/metamodel/expression/NumericOperatorExpressionRecord.java
new file mode 100644
index 00000000..b2fe9487
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/NumericOperatorExpressionRecord.java
@@ -0,0 +1,25 @@
+/*
+ * 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.expression;
+
+import jakarta.data.metamodel.NumericExpression;
+
+record NumericOperatorExpressionRecord<T, N extends Number & Comparable<N>>
+        (Operator operator, NumericExpression<T,N> left, NumericExpression<T,N> right)
+        implements NumericOperatorExpression<T,N> {
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/StringLiteral.java b/api/src/main/java/jakarta/data/metamodel/expression/StringLiteral.java
new file mode 100644
index 00000000..b116e129
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/StringLiteral.java
@@ -0,0 +1,28 @@
+/*
+ * 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.expression;
+
+import jakarta.data.metamodel.TextExpression;
+
+public interface StringLiteral<T> extends TextExpression<T> {
+    String value();
+
+    static <T> StringLiteral<T> of(String value) {
+        return new StringLiteralRecord<T>(value);
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/StringLiteralRecord.java b/api/src/main/java/jakarta/data/metamodel/expression/StringLiteralRecord.java
new file mode 100644
index 00000000..83a8067e
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/StringLiteralRecord.java
@@ -0,0 +1,23 @@
+/*
+ * 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.expression;
+
+record StringLiteralRecord<T>(String value)
+        implements StringLiteral<T> {
+
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/TextFunctionExpression.java b/api/src/main/java/jakarta/data/metamodel/expression/TextFunctionExpression.java
new file mode 100644
index 00000000..f470fd88
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/TextFunctionExpression.java
@@ -0,0 +1,48 @@
+/*
+ * 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.expression;
+
+import jakarta.data.metamodel.TextExpression;
+
+import java.util.List;
+
+public interface TextFunctionExpression<T>
+        extends FunctionExpression<T,String>, TextExpression<T> {
+
+    String CONCAT = "concat";
+    String UPPER = "upper";
+    String LOWER = "lower";
+    String LEFT = "left";
+    String RIGHT = "right";
+
+    static <T> TextFunctionExpression<T> of(String name, TextExpression<T> argument) {
+        return new TextFunctionExpressionRecord<>(name, List.of(argument));
+    }
+    static <T> TextFunctionExpression<T> of(String name, TextExpression<T> left, String right) {
+        return new TextFunctionExpressionRecord<>(name, List.of(left, StringLiteral.of(right)));
+    }
+    static <T> TextFunctionExpression<T> of(String name, String left, TextExpression<T> right) {
+        return new TextFunctionExpressionRecord<>(name, List.of(StringLiteral.of(left), right));
+    }
+    static <T> TextFunctionExpression<T> of(String name, TextExpression<T> left, TextExpression<T> right) {
+        return new TextFunctionExpressionRecord<>(name, List.of(left, right));
+    }
+    static <T> TextFunctionExpression<T> of(String name, TextExpression<T> left, int literal) {
+        return new TextFunctionExpressionRecord<>(name, List.of(left, NumericLiteral.of(literal)));
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/TextFunctionExpressionRecord.java b/api/src/main/java/jakarta/data/metamodel/expression/TextFunctionExpressionRecord.java
new file mode 100644
index 00000000..f2fb029c
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/TextFunctionExpressionRecord.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.expression;
+
+import jakarta.data.metamodel.Expression;
+
+import java.util.List;
+
+record TextFunctionExpressionRecord<T>(String name, List<? extends Expression<T,?>> arguments)
+        implements TextFunctionExpression<T> {
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/impl/ComparableAttributeRecord.java b/api/src/main/java/jakarta/data/metamodel/impl/ComparableAttributeRecord.java
index 5c6e93c2..97bd84e5 100644
--- a/api/src/main/java/jakarta/data/metamodel/impl/ComparableAttributeRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/impl/ComparableAttributeRecord.java
@@ -17,18 +17,8 @@
  */
 package jakarta.data.metamodel.impl;
 
-import jakarta.data.Sort;
 import jakarta.data.metamodel.ComparableAttribute;
 
-public record ComparableAttributeRecord<T,V extends Comparable<V>>(String name)
+public record ComparableAttributeRecord<T,V extends Comparable<?>>(String name)
         implements ComparableAttribute<T,V> {
-    @Override
-    public Sort<T> asc() {
-        return Sort.asc(name);
-    }
-
-    @Override
-    public Sort<T> desc() {
-        return Sort.desc(name);
-    }
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/impl/NavigableAttributeRecord.java b/api/src/main/java/jakarta/data/metamodel/impl/NavigableAttributeRecord.java
new file mode 100644
index 00000000..cdf03db1
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/impl/NavigableAttributeRecord.java
@@ -0,0 +1,24 @@
+/*
+ * 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.impl;
+
+import jakarta.data.metamodel.NavigableAttribute;
+
+public record NavigableAttributeRecord<T,U>(String name)
+        implements NavigableAttribute<T,U> {
+}
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/metamodel/impl/NumericAttributeRecord.java b/api/src/main/java/jakarta/data/metamodel/impl/NumericAttributeRecord.java
new file mode 100644
index 00000000..3529dab9
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/impl/NumericAttributeRecord.java
@@ -0,0 +1,24 @@
+/*
+ * 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.impl;
+
+import jakarta.data.metamodel.NumericAttribute;
+
+public record NumericAttributeRecord<T,V extends Number & Comparable<V>>(String name)
+        implements NumericAttribute<T,V> {
+}
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/metamodel/impl/SortableAttributeRecord.java b/api/src/main/java/jakarta/data/metamodel/impl/SortableAttributeRecord.java
index 73f1df79..124d9260 100644
--- a/api/src/main/java/jakarta/data/metamodel/impl/SortableAttributeRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/impl/SortableAttributeRecord.java
@@ -17,7 +17,6 @@
  */
 package jakarta.data.metamodel.impl;
 
-import jakarta.data.Sort;
 import jakarta.data.metamodel.SortableAttribute;
 
 /**
@@ -28,14 +27,5 @@ import jakarta.data.metamodel.SortableAttribute;
  */
 public record SortableAttributeRecord<T>(String name)
         implements SortableAttribute<T> {
-    @Override
-    public Sort<T> asc() {
-        return Sort.asc(name);
-    }
-
-    @Override
-    public Sort<T> desc() {
-        return Sort.desc(name);
-    }
 }
 
diff --git a/api/src/main/java/jakarta/data/metamodel/impl/TextAttributeRecord.java b/api/src/main/java/jakarta/data/metamodel/impl/TextAttributeRecord.java
index 4fe32575..0ccc1831 100644
--- a/api/src/main/java/jakarta/data/metamodel/impl/TextAttributeRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/impl/TextAttributeRecord.java
@@ -17,7 +17,6 @@
  */
 package jakarta.data.metamodel.impl;
 
-import jakarta.data.Sort;
 import jakarta.data.metamodel.TextAttribute;
 
 /**
@@ -28,23 +27,4 @@ import jakarta.data.metamodel.TextAttribute;
  */
 public record TextAttributeRecord<T>(String name)
         implements TextAttribute<T> {
-    @Override
-    public Sort<T> asc() {
-        return Sort.asc(name);
-    }
-
-    @Override
-    public Sort<T> desc() {
-        return Sort.desc(name);
-    }
-
-    @Override
-    public Sort<T> ascIgnoreCase() {
-        return Sort.ascIgnoreCase(name);
-    }
-
-    @Override
-    public Sort<T> descIgnoreCase() {
-        return Sort.descIgnoreCase(name);
-    }
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/path/ComparablePath.java b/api/src/main/java/jakarta/data/metamodel/path/ComparablePath.java
new file mode 100644
index 00000000..72492a8a
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/ComparablePath.java
@@ -0,0 +1,31 @@
+/*
+ * 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.path;
+
+import jakarta.data.metamodel.ComparableAttribute;
+import jakarta.data.metamodel.ComparableExpression;
+import jakarta.data.metamodel.NavigableExpression;
+import jakarta.data.metamodel.Path;
+
+public interface ComparablePath<T,U,C extends Comparable<?>>
+    extends Path<T,U>, ComparableExpression<T,C> {
+    static <T,U,C extends Comparable<C>> ComparablePath<T, U, C>
+    of(NavigableExpression<T, U> expression, ComparableAttribute<U,C> attribute) {
+        return new ComparablePathRecord<>(expression, attribute);
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/path/ComparablePathRecord.java b/api/src/main/java/jakarta/data/metamodel/path/ComparablePathRecord.java
new file mode 100644
index 00000000..03142e56
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/ComparablePathRecord.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.path;
+
+import jakarta.data.metamodel.ComparableAttribute;
+import jakarta.data.metamodel.ComparableExpression;
+import jakarta.data.metamodel.NavigableExpression;
+
+record ComparablePathRecord<T,U,C extends Comparable<?>>
+        (NavigableExpression<T,U> expression, ComparableAttribute<U, C> attribute)
+        implements ComparableExpression<T,C>, ComparablePath<T,U,C> {
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/path/NavigablePath.java b/api/src/main/java/jakarta/data/metamodel/path/NavigablePath.java
new file mode 100644
index 00000000..76c1541a
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/NavigablePath.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.path;
+
+import jakarta.data.metamodel.NavigableAttribute;
+import jakarta.data.metamodel.NavigableExpression;
+import jakarta.data.metamodel.Path;
+
+public interface NavigablePath<T,U,V>
+        extends Path<T,U>, NavigableExpression<T,V> {
+    static <T,U,V> NavigablePath<T,U,V> of(NavigableExpression<T, U> expression, NavigableAttribute<U,V> attribute) {
+        return new NavigablePathRecord<>(expression, attribute);
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/path/NavigablePathRecord.java b/api/src/main/java/jakarta/data/metamodel/path/NavigablePathRecord.java
new file mode 100644
index 00000000..d989e417
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/NavigablePathRecord.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.path;
+
+import jakarta.data.metamodel.NavigableAttribute;
+import jakarta.data.metamodel.NavigableExpression;
+
+record NavigablePathRecord<T,U,V>
+        (NavigableExpression<T,U> expression, NavigableAttribute<U,V> attribute)
+        implements NavigableExpression<T,V>, NavigablePath<T,U,V> {
+
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/path/NumericPath.java b/api/src/main/java/jakarta/data/metamodel/path/NumericPath.java
new file mode 100644
index 00000000..23f348f3
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/NumericPath.java
@@ -0,0 +1,31 @@
+/*
+ * 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.path;
+
+import jakarta.data.metamodel.NavigableExpression;
+import jakarta.data.metamodel.NumericAttribute;
+import jakarta.data.metamodel.NumericExpression;
+import jakarta.data.metamodel.Path;
+
+public interface NumericPath<T,U,N extends Number & Comparable<N>>
+        extends Path<T,U>, NumericExpression<T,N> {
+    static <T,U, N extends Number & Comparable<N>> NumericPath<T, U, N>
+    of(NavigableExpression<T, U> expression, NumericAttribute<U, N> attribute) {
+        return new NumericPathRecord<>(expression, attribute);
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/path/NumericPathRecord.java b/api/src/main/java/jakarta/data/metamodel/path/NumericPathRecord.java
new file mode 100644
index 00000000..f2b20534
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/NumericPathRecord.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.path;
+
+import jakarta.data.metamodel.NavigableExpression;
+import jakarta.data.metamodel.NumericAttribute;
+import jakarta.data.metamodel.NumericExpression;
+
+record NumericPathRecord<T,U,N extends Number & Comparable<N>>
+        (NavigableExpression<T,U> expression, NumericAttribute<U,N> attribute)
+        implements NumericExpression<T,N>, NumericPath<T,U,N> {
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/path/TextPath.java b/api/src/main/java/jakarta/data/metamodel/path/TextPath.java
new file mode 100644
index 00000000..b672fe6c
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/TextPath.java
@@ -0,0 +1,30 @@
+/*
+ * 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.path;
+
+import jakarta.data.metamodel.NavigableExpression;
+import jakarta.data.metamodel.Path;
+import jakarta.data.metamodel.TextAttribute;
+import jakarta.data.metamodel.TextExpression;
+
+public interface TextPath<T,U>
+        extends Path<T,U>, TextExpression<T> {
+    static <T,U> TextPath<T,U> of(NavigableExpression<T, U> expression, TextAttribute<U> attribute) {
+        return new TextPathRecord<>(expression, attribute);
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/path/TextPathRecord.java b/api/src/main/java/jakarta/data/metamodel/path/TextPathRecord.java
new file mode 100644
index 00000000..264f8d2b
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/TextPathRecord.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.path;
+
+import jakarta.data.metamodel.NavigableExpression;
+import jakarta.data.metamodel.TextAttribute;
+import jakarta.data.metamodel.TextExpression;
+
+record TextPathRecord<T,U>
+        (NavigableExpression<T,U> expression, TextAttribute<U> attribute)
+        implements TextExpression<T>, TextPath<T,U> {
+}
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 ba98a383..fefaccad 100644
--- a/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestriction.java
+++ b/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestriction.java
@@ -17,6 +17,7 @@
  */
 package jakarta.data.metamodel.restrict;
 
+import jakarta.data.metamodel.Expression;
 import jakarta.data.metamodel.constraint.Constraint;
 
 public interface BasicRestriction<T, V> extends Restriction<T> {
@@ -25,5 +26,10 @@ public interface BasicRestriction<T, V> extends Restriction<T> {
     @Override
     BasicRestriction<T, V> negate();
 
+    Expression<T,V> expression();
     Constraint<V> constraint();
+
+    static <T,V> BasicRestriction<T, V> of(Expression<T,V> expression,  Constraint<V> constraint) {
+        return new BasicRestrictionRecord<>(expression, constraint);
+    }
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/BasicRestrictionRecord.java b/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestrictionRecord.java
similarity index 68%
rename from api/src/main/java/jakarta/data/metamodel/BasicRestrictionRecord.java
rename to api/src/main/java/jakarta/data/metamodel/restrict/BasicRestrictionRecord.java
index f18854db..56132f60 100644
--- a/api/src/main/java/jakarta/data/metamodel/BasicRestrictionRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/restrict/BasicRestrictionRecord.java
@@ -15,28 +15,34 @@
  *
  *  SPDX-License-Identifier: Apache-2.0
  */
-package jakarta.data.metamodel;
+package jakarta.data.metamodel.restrict;
 
 // Internal implementation class.
 // The proper way for users to obtain instances is via
 // the static metamodel or Restrict.* methods 
 
+import jakarta.data.metamodel.Attribute;
+import jakarta.data.metamodel.Expression;
 import jakarta.data.metamodel.constraint.Constraint;
-import jakarta.data.metamodel.restrict.BasicRestriction;
 
 import java.util.Objects;
 
-record BasicRestrictionRecord<T, V>(String attribute, Constraint<V> constraint)
+record BasicRestrictionRecord<T, V>(Expression<T,V> expression, Constraint<V> constraint)
         implements BasicRestriction<T, V> {
 
     BasicRestrictionRecord {
-        Objects.requireNonNull(attribute, "Attribute must not be null");
+        Objects.requireNonNull(expression, "Expression must not be null");
         Objects.requireNonNull(constraint, "Constraint must not be null");
     }
 
+    @Override
+    public String attribute() {
+        return expression instanceof Attribute<?> att ? att.name() : null;
+    }
+
     @Override
     public BasicRestriction<T, V> negate() {
-        return new BasicRestrictionRecord<>(attribute, constraint.negate());
+        return BasicRestriction.of(expression, constraint.negate());
     }
 
     /**
@@ -48,6 +54,7 @@ record BasicRestrictionRecord<T, V>(String attribute, Constraint<V> constraint)
      */
     @Override
     public String toString() {
-        return attribute + ' ' + constraint;
+        return (expression instanceof Attribute<?> att ? att.name() : expression.toString())
+                + " " + 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 3eb70f4c..515fd112 100644
--- a/api/src/main/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecord.java
+++ b/api/src/main/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecord.java
@@ -19,8 +19,6 @@ 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/module-info.java b/api/src/main/java/module-info.java
index 902d3689..60c33405 100644
--- a/api/src/main/java/module-info.java
+++ b/api/src/main/java/module-info.java
@@ -1211,6 +1211,7 @@ module jakarta.data {
     exports jakarta.data.metamodel.impl;
     exports jakarta.data.metamodel.restrict;
     exports jakarta.data.metamodel.constraint;
+    exports jakarta.data.metamodel.expression;
     exports jakarta.data.page;
     exports jakarta.data.page.impl;
     exports jakarta.data.repository;


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 c93fd65b..761901ef 100644
--- a/api/src/test/java/jakarta/data/metamodel/AttributeTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/AttributeTest.java
@@ -29,7 +29,6 @@ import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
 
 import jakarta.data.metamodel.restrict.BasicRestriction;
-import jakarta.data.metamodel.restrict.Restriction;
 
 import java.util.Set;
 
@@ -47,40 +46,40 @@ class AttributeTest {
     private final BasicAttribute<Person, String> testAttribute = () -> "testAttribute";
     @Test
     void shouldCreateEqualToRestriction() {
-        Restriction<Person> restriction = testAttribute.equalTo("testValue");
+        BasicRestriction<Person, String> restriction =
+                (BasicRestriction<Person, String>) testAttribute.equalTo("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            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(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(EqualTo.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.equalTo("testValue"));
         });
     }
 
     @Test
     void shouldCreateNotEqualToRestriction() {
-        Restriction<Person> restriction = testAttribute.notEqualTo("testValue");
+        BasicRestriction<Person, String> restriction =
+                (BasicRestriction<Person, String>) testAttribute.notEqualTo("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
-            soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(basic.constraint()).isInstanceOf(NotEqualTo.class);
-            soft.assertThat(basic.constraint()).isEqualTo(NotEqualTo.value("testValue"));
+            soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotEqualTo.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(NotEqualTo.value("testValue"));
         });
     }
 
     @Test
     void shouldCreateInRestriction() {
-        Restriction<Person> restriction = testAttribute.in(Set.of("value1", "value2"));
+        BasicRestriction<Person, String> restriction =
+                (BasicRestriction<Person, String>) testAttribute.in(Set.of("value1", "value2"));
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            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(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(In.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.in("value1", "value2"));
         });
     }
 
@@ -93,14 +92,14 @@ class AttributeTest {
 
     @Test
     void shouldCreateNotInRestriction() {
-        Restriction<Person> restriction = testAttribute.notIn(Set.of("value1", "value2"));
+        BasicRestriction<Person, String> restriction =
+                (BasicRestriction<Person, String>) testAttribute.notIn(Set.of("value1", "value2"));
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
-            soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(basic.constraint()).isInstanceOf(NotIn.class);
-            soft.assertThat(basic.constraint()).isEqualTo(NotIn.values("value1", "value2"));
+            soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(NotIn.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(NotIn.values("value1", "value2"));
         });
     }
 
@@ -113,25 +112,25 @@ class AttributeTest {
 
     @Test
     void shouldCreateIsNullRestriction() {
-        Restriction<Person> restriction = testAttribute.isNull();
+        BasicRestriction<Person, String> restriction =
+                (BasicRestriction<Person, String>) testAttribute.isNull();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
-            soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(basic.constraint()).isInstanceOf(Null.class);
+            soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(Null.class);
         });
     }
 
     @Test
     void shouldCreateNotNullRestriction() {
-        Restriction<Person> restriction = testAttribute.notNull();
+        BasicRestriction<Person, String> restriction =
+                (BasicRestriction<Person, String>) testAttribute.notNull();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<Person, String> basic = (BasicRestriction<Person, String>) restriction;
-            soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(basic.constraint()).isInstanceOf(NotNull.class);
+            soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.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 437981e7..17e1fc32 100644
--- a/api/src/test/java/jakarta/data/metamodel/SortableAttributeTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/SortableAttributeTest.java
@@ -17,7 +17,6 @@
  */
 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;
@@ -25,7 +24,6 @@ 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.Restriction;
 
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
@@ -42,16 +40,6 @@ class SortableAttributeTest {
 
     //it ignores the implementation of the SortableAttribute interface and uses an anonymous class to test the methods
     private final ComparableAttribute<Person, Integer> testAttribute = new ComparableAttribute<>() {
-        @Override
-        public Sort<Person> asc() {
-           throw new UnsupportedOperationException("It is not the focus of this test");
-        }
-
-        @Override
-        public Sort<Person> desc() {
-            throw new UnsupportedOperationException("It is not the focus of this test");
-        }
-
         @Override
         public String name() {
             return "testAttribute";
@@ -60,67 +48,66 @@ class SortableAttributeTest {
 
     @Test
     void shouldCreateGreaterThanRestriction() {
-        Restriction<Person> restriction = testAttribute.greaterThan(10);
+        BasicRestriction<Person, Integer> restriction =
+                (BasicRestriction<Person, Integer>) testAttribute.greaterThan(10);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            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(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(GreaterThan.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.greaterThan(10));
         });
     }
 
     @Test
     void shouldCreateGreaterThanEqualRestriction() {
-        Restriction<Person> restriction = testAttribute.greaterThanEqual(10);
+        BasicRestriction<Person, Integer> restriction =
+                (BasicRestriction<Person, Integer>) testAttribute.greaterThanEqual(10);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            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(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(GreaterThanOrEqual.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.greaterThanOrEqual(10));
         });
     }
 
     @Test
     void shouldCreateLessThanRestriction() {
-        Restriction<Person> restriction = testAttribute.lessThan(10);
+        BasicRestriction<Person, Integer> restriction =
+                (BasicRestriction<Person, Integer>) testAttribute.lessThan(10);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            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(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(LessThan.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.lessThan(10));
         });
     }
 
     @Test
     void shouldCreateLessThanOrEqualRestriction() {
-        Restriction<Person> restriction = testAttribute.lessThanEqual(10);
+        BasicRestriction<Person, Integer> restriction =
+                (BasicRestriction<Person, Integer>) testAttribute.lessThanEqual(10);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            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(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(LessThanOrEqual.class);
+            soft.assertThat(restriction.constraint()).isEqualTo(Constraint.lessThanOrEqual(10));
         });
     }
 
     @Test
     void shouldCreateBetweenRestriction() {
-        Restriction<Person> restriction = testAttribute.between(5, 15);
+        BasicRestriction<Person, Integer> restriction =
+                (BasicRestriction<Person, Integer>) testAttribute.between(5, 15);
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction).isInstanceOf(BasicRestriction.class);
-            BasicRestriction<Person, Integer> basic = (BasicRestriction<Person, Integer>) restriction;
-
-            soft.assertThat(basic.attribute()).isEqualTo("testAttribute");
-            soft.assertThat(basic.constraint()).isInstanceOf(Between.class);
-            soft.assertThat(basic.constraint()).isEqualTo(Constraint.between(5,15));
+            soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
+            soft.assertThat(restriction.constraint()).isInstanceOf(Between.class);
+            soft.assertThat(restriction.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 82d7579e..7ec6e48d 100644
--- a/api/src/test/java/jakarta/data/metamodel/TextAttributeTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/TextAttributeTest.java
@@ -17,46 +17,21 @@
  */
 package jakarta.data.metamodel;
 
-import jakarta.data.Sort;
 import jakarta.data.metamodel.constraint.Like;
 import jakarta.data.metamodel.constraint.NotLike;
-import jakarta.data.metamodel.restrict.TextRestriction;
+import jakarta.data.metamodel.restrict.BasicRestriction;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
 
 class TextAttributeTest {
 
 
-    private final TextAttribute<String> testAttribute = new TextAttribute<String>() {
-        @Override
-        public Sort<String> ascIgnoreCase() {
-            throw new UnsupportedOperationException("Not the focus of this test.");
-        }
-
-        @Override
-        public Sort<String> descIgnoreCase() {
-            throw new UnsupportedOperationException("Not the focus of this test.");
-        }
-
-        @Override
-        public Sort<String> asc() {
-            throw new UnsupportedOperationException("Not the focus of this test.");
-        }
-
-        @Override
-        public Sort<String> desc() {
-            throw new UnsupportedOperationException("Not the focus of this test.");
-        }
-
-        @Override
-        public String name() {
-            return "testAttribute";
-        }
-    };
+    private final TextAttribute<String> testAttribute = () -> "testAttribute";
 
     @Test
     void shouldCreateContainsRestriction() {
-        TextRestriction<String> restriction = testAttribute.contains("testValue");
+        BasicRestriction<String,String> restriction =
+                (BasicRestriction<String, String>) testAttribute.contains("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
@@ -67,7 +42,8 @@ class TextAttributeTest {
 
     @Test
     void shouldCreateStartsWithRestriction() {
-        TextRestriction<String> restriction = testAttribute.startsWith("testValue");
+        BasicRestriction<String,String> restriction =
+                (BasicRestriction<String, String>) testAttribute.startsWith("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
@@ -78,7 +54,8 @@ class TextAttributeTest {
 
     @Test
     void shouldCreateEndsWithRestriction() {
-        TextRestriction<String> restriction = testAttribute.endsWith("testValue");
+        BasicRestriction<String,String> restriction =
+                (BasicRestriction<String,String>) testAttribute.endsWith("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
@@ -89,7 +66,8 @@ class TextAttributeTest {
 
     @Test
     void shouldCreateLikeRestriction() {
-        TextRestriction<String> restriction = testAttribute.like("%test%");
+        BasicRestriction<String,String> restriction =
+                (BasicRestriction<String, String>) testAttribute.like("%test%");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
@@ -100,7 +78,8 @@ class TextAttributeTest {
 
     @Test
     void shouldCreateNotContainsRestriction() {
-        TextRestriction<String> restriction = testAttribute.notContains("testValue");
+        BasicRestriction<String,String> restriction =
+                (BasicRestriction<String, String>) testAttribute.notContains("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
@@ -111,7 +90,8 @@ class TextAttributeTest {
 
     @Test
     void shouldCreateNotLikeRestriction() {
-        TextRestriction<String> restriction = testAttribute.notLike("%test%");
+        BasicRestriction<String,String> restriction =
+                (BasicRestriction<String, String>) testAttribute.notLike("%test%");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
@@ -122,7 +102,8 @@ class TextAttributeTest {
 
     @Test
     void shouldCreateNotStartsWithRestriction() {
-        TextRestriction<String> restriction = testAttribute.notStartsWith("testValue");
+        BasicRestriction<String,String> restriction =
+                (BasicRestriction<String, String>) testAttribute.notStartsWith("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
@@ -133,7 +114,8 @@ class TextAttributeTest {
 
     @Test
     void shouldCreateNotEndsWithRestriction() {
-        TextRestriction<String> restriction = testAttribute.notEndsWith("testValue");
+        BasicRestriction<String,String> restriction =
+                (BasicRestriction<String, String>) testAttribute.notEndsWith("testValue");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("testAttribute");
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 afdf04b8..2c5e2171 100644
--- a/api/src/test/java/jakarta/data/metamodel/restrict/BasicRestrictionRecordTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/restrict/BasicRestrictionRecordTest.java
@@ -146,7 +146,7 @@ class BasicRestrictionRecordTest {
 
     @Test
     void shouldThrowExceptionWhenValueIsNull() {
-        assertThatThrownBy(() -> _Book.title.equalTo(null))
+        assertThatThrownBy(() -> _Book.title.equalTo((String) null))
                 .isInstanceOf(NullPointerException.class)
                 .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 ce81d427..892093bb 100644
--- a/api/src/test/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecordTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/restrict/CompositeRestrictionRecordTest.java
@@ -19,8 +19,6 @@ 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 org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
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 5db6e176..50cc220c 100644
--- a/api/src/test/java/jakarta/data/metamodel/restrict/RestrictTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/restrict/RestrictTest.java
@@ -93,7 +93,8 @@ class RestrictTest {
 
     @Test
     void shouldCreateEqualToStringRestriction() {
-        TextRestriction<Employee> restriction = _Employee.position.equalTo("Software Engineer");
+        BasicRestriction<Employee,String> restriction =
+                (BasicRestriction<Employee, String>) _Employee.position.equalTo("Software Engineer");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("position");
@@ -104,7 +105,8 @@ class RestrictTest {
 
     @Test
     void shouldCreateNotEqualToStringRestriction() {
-        TextRestriction<Employee> restriction = _Employee.position.notEqualTo("Manager");
+        BasicRestriction<Employee,String> restriction =
+                (BasicRestriction<Employee, String>) _Employee.position.notEqualTo("Manager");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("position");
@@ -132,7 +134,8 @@ class RestrictTest {
 
     @Test
     void shouldCreateContainsRestriction() {
-        TextRestriction<Employee> restriction = _Employee.position.contains("Manager");
+        BasicRestriction<Employee,String> restriction =
+                (BasicRestriction<Employee, String>) _Employee.position.contains("Manager");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("position");
@@ -143,7 +146,8 @@ class RestrictTest {
 
     @Test
     void shouldCreateNegatedContainsRestriction() {
-        TextRestriction<Employee> restriction = _Employee.position.notContains("Director");
+        BasicRestriction<Employee,String> restriction =
+                (BasicRestriction<Employee, String>) _Employee.position.notContains("Director");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("position");
@@ -154,7 +158,8 @@ class RestrictTest {
 
     @Test
     void shouldCreateStartsWithRestriction() {
-        TextRestriction<Employee> restriction = _Employee.position.startsWith("Director");
+        BasicRestriction<Employee,String> restriction =
+                (BasicRestriction<Employee, String>) _Employee.position.startsWith("Director");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("position");
@@ -165,7 +170,8 @@ class RestrictTest {
 
     @Test
     void shouldCreateNegatedStartsWithRestriction() {
-        TextRestriction<Employee> restriction = _Employee.position.notStartsWith("Manager");
+        BasicRestriction<Employee,String> restriction =
+                (BasicRestriction<Employee, String>) _Employee.position.notStartsWith("Manager");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("position");
@@ -176,7 +182,8 @@ class RestrictTest {
 
     @Test
     void shouldCreateEndsWithRestriction() {
-        TextRestriction<Employee> restriction = _Employee.position.endsWith("Manager");
+        BasicRestriction<Employee,String> restriction =
+                (BasicRestriction<Employee,String>) _Employee.position.endsWith("Manager");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("position");
@@ -187,7 +194,8 @@ class RestrictTest {
 
     @Test
     void shouldCreateNegatedEndsWithRestriction() {
-        TextRestriction<Employee> restriction = _Employee.position.notEndsWith("Supervisor");
+        BasicRestriction<Employee,String> restriction =
+                (BasicRestriction<Employee, String>) _Employee.position.notEndsWith("Supervisor");
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(restriction.attribute()).isEqualTo("position");
@@ -231,7 +239,7 @@ class RestrictTest {
 
     @Test
     void shouldEscapeToLikePatternCorrectly() {
-        Like like = (Like) _Employee.position.endsWith("test_value").constraint();
+        Like like = (Like) ((BasicRestriction<Employee,String>) _Employee.position.endsWith("test_value")).constraint();
         String result = like.pattern();
 
         assertThat(result).isEqualTo("%test\\_value");
@@ -240,7 +248,8 @@ class RestrictTest {
     @Test
     void shouldIgnoreCase() {
         // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
-        TextRestriction<Employee> restriction = _Employee.position.contains("SOFTWARE");
+        BasicRestriction<Employee,String> restriction =
+                (BasicRestriction<Employee, String>) _Employee.position.contains("SOFTWARE");
         //        .ignoreCase();
 
         SoftAssertions.assertSoftly(soft -> {
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 d702480d..367683ce 100644
--- a/api/src/test/java/jakarta/data/metamodel/restrict/TextRestrictionRecordTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/restrict/TextRestrictionRecordTest.java
@@ -52,7 +52,8 @@ class TextRestrictionRecordTest {
 
     @Test
     void shouldCreateTextRestrictionWithDefaultValues() {
-        TextRestriction<Book> restriction = _Book.title.like("%Java%");
+        BasicRestriction<Book,String> restriction =
+                (BasicRestriction<Book, String>) _Book.title.like("%Java%");
 
         Like constraint = (Like) restriction.constraint();
 
@@ -67,7 +68,8 @@ class TextRestrictionRecordTest {
 
     @Test
     void shouldCreateTextRestrictionWithExplicitNegation() {
-        TextRestriction<Book> restriction = _Book.title.like("%Java%").negate();
+        BasicRestriction<Book,String> restriction =
+                (BasicRestriction<Book, String>) _Book.title.like("%Java%").negate();
 
         NotLike constraint = (NotLike) restriction.constraint();
 
@@ -82,10 +84,11 @@ class TextRestrictionRecordTest {
 
     @Test
     void shouldIgnoreCaseForTextRestriction() {
-        TextRestriction<Book> restriction = _Book.title.like("%Java%");
+        BasicRestriction<Book,String> restriction =
+                (BasicRestriction<Book, String>) _Book.title.like("%Java%");
 
         // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
-        //TextRestriction<String> caseInsensitiveRestriction = restriction.ignoreCase();
+        //BasicRestriction<Book,String> caseInsensitiveRestriction = restriction.ignoreCase();
         Like constraint = (Like) restriction.constraint();
 
         SoftAssertions.assertSoftly(soft -> {
@@ -98,7 +101,8 @@ class TextRestrictionRecordTest {
 
     @Test
     void shouldCreateTextRestrictionWithEscapedValue() {
-        TextRestriction<Book> restriction = _Book.title.like("%Java%");
+        BasicRestriction<Book,String> restriction =
+                (BasicRestriction<Book, String>) _Book.title.like("%Java%");
 
         Like constraint = (Like) restriction.constraint();
 
@@ -113,7 +117,8 @@ class TextRestrictionRecordTest {
 
     @Test
     void shouldCreateTextRestrictionWithCustomWildcards() {
-        TextRestriction<Book> restriction = _Book.title.like("*Java??", '?', '*', '$');
+        BasicRestriction<Book,String> restriction =
+                (BasicRestriction<Book, String>) _Book.title.like("*Java??", '?', '*', '$');
 
         Like constraint = (Like) restriction.constraint();
 
@@ -128,11 +133,12 @@ class TextRestrictionRecordTest {
 
     @Test
     void shouldNegateLikeRestriction() {
-        TextRestriction<Book> likeJakartaEE = _Book.title.endsWith("Jakarta EE");
-        TextRestriction<Book> notLikeJakartaEE = likeJakartaEE.negate();
+        BasicRestriction<Book,String> likeJakartaEE =
+                (BasicRestriction<Book, String>) _Book.title.endsWith("Jakarta EE");
+        BasicRestriction<Book,String> notLikeJakartaEE = likeJakartaEE.negate();
         // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
-        //TextRestriction<Book> anyCaseNotLikeJakartaEE = likeJakartaEE.ignoreCase().negate();
-        //TextRestriction<Book> notLikeJakartaEEAnyCase = likeJakartaEE.negate().ignoreCase();
+        //BasicRestriction<Book,String> anyCaseNotLikeJakartaEE = likeJakartaEE.ignoreCase().negate();
+        //BasicRestriction<Book,String> notLikeJakartaEEAnyCase = likeJakartaEE.negate().ignoreCase();
 
         //SoftAssertions.assertSoftly(soft -> {
         //    soft.assertThat(likeJakartaEE.isCaseSensitive()).isTrue();
@@ -144,9 +150,10 @@ class TextRestrictionRecordTest {
 
     @Test
     void shouldNegateNegatedRestriction() {
-        TextRestriction<Book> endsWithJakartaEE = _Book.title.endsWith("Jakarta EE");
-        TextRestriction<Book> notEndsWithJakartaEE = endsWithJakartaEE.negate();
-        TextRestriction<Book> notNotEndsWithJakartaEE = notEndsWithJakartaEE.negate();
+        BasicRestriction<Book,String> endsWithJakartaEE =
+                (BasicRestriction<Book, String>) _Book.title.endsWith("Jakarta EE");
+        BasicRestriction<Book,String> notEndsWithJakartaEE = endsWithJakartaEE.negate();
+        BasicRestriction<Book,String> notNotEndsWithJakartaEE = notEndsWithJakartaEE.negate();
 
         SoftAssertions.assertSoftly(soft -> {
             soft.assertThat(endsWithJakartaEE.constraint()).isInstanceOf(Like.class);
@@ -165,8 +172,10 @@ class TextRestrictionRecordTest {
 
     @Test
     void shouldOutputToString() {
-        TextRestriction<Book> titleRestriction = _Book.title.contains("Jakarta Data");
-        TextRestriction<Book> authorRestriction = _Book.author.equalTo("Myself")
+        BasicRestriction<Book,String> titleRestriction =
+                (BasicRestriction<Book, String>) _Book.title.contains("Jakarta Data");
+        BasicRestriction<Book,String> authorRestriction =
+                (BasicRestriction<Book, String>) _Book.author.equalTo("Myself")
                 // TODO TextRestriction.ignoreCase vs TextAttribute.upper/lowercased
                 //        .ignoreCase()
                         .negate();
@@ -183,7 +192,8 @@ class TextRestrictionRecordTest {
 
     @Test
     void shouldSupportNegationForTextRestriction() {
-        TextRestriction<Book> restriction = _Book.author.equalTo("John Doe").negate();
+        BasicRestriction<Book,String> restriction =
+                (BasicRestriction<Book, String>) _Book.author.equalTo("John Doe").negate();
 
         NotEqualTo<String> constraint = (NotEqualTo<String>) restriction.constraint();