diff --git a/CHANGELOG.md b/CHANGELOG.md
index 355684b29..8409dd25d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,7 @@ A join widens the query: from the join onward, every clause accepts paths from a
```
In Java the lambda escalation goes with it: `.where(it -> it.whereAny(UserRole_.user, EQUALS, user))` becomes the typed overload `.where(UserRole_.user, EQUALS, user)`. A path on an entity that is not part of the query fails when the query is built, with the error naming the entity, the query root, and — when the table appears more than once — the paths that pin it.
+- The predicate combinators follow the same model: `andAny` and `orAny` are removed from every API, and `and`/`or` inherit the query root. In Java every predicate carries the builder's root, so the plain calls absorb the `Any` twins as a rename per call site. In Kotlin, `and`/`or` live in the `whereBuilder { }` scope: a narrow scope combines predicates within the root graph, and a join widens the root so the same expression combines predicates across joined entities — `(Pet_.name eq "Leo") and (Owner_.lastName eq "Davis")` after joining `Owner`. Outside a builder scope, the top-level `and`/`or` extensions combine predicates that share a root.
- Added `widen()` and `narrow(rootType)`, the two directions of the model made explicit: `widen()` widens without a join, admitting short-form references to entities of the query's graph on a query that joins nothing, and `narrow` restores the root after a join for the operations defined relative to it, verified against the query's FROM table. Those are `resultGroupedBy`, which types the map key, and `scroll`, whose key has to identify one row of the root — a unique key on a joined table does not, so scrolling a joined query narrows first.
- Renamed `typed(pkType)` to `typedId(pkType)` — it types the erased primary-key parameter, while `narrow` types the root — and added its missing null check.
- `fetch(...)` comes right after `select()`, before any join, enforced at compile time: resolving references is defined relative to the root, and a join widens the builder past it.
diff --git a/storm-core/src/main/java/st/orm/core/template/PredicateBuilder.java b/storm-core/src/main/java/st/orm/core/template/PredicateBuilder.java
index 05b17d050..5b3b0e122 100644
--- a/storm-core/src/main/java/st/orm/core/template/PredicateBuilder.java
+++ b/storm-core/src/main/java/st/orm/core/template/PredicateBuilder.java
@@ -31,23 +31,13 @@ public interface PredicateBuilder This method combines the specified predicate with existing predicates using an AND operation, ensuring
- * that all added conditions must be true. This method combines the specified predicate with existing predicates using an AND operation, ensuring
- * that all added conditions must be true.
This method combines the specified predicate with existing predicates using an OR operation, allowing any - * of the added conditions to be true.
- * - * @param predicate the predicate to add. - * @return the predicate builder. - */ - PredicateBuilderThis method combines the specified predicate with existing predicates using an OR operation, allowing any - * of the added conditions to be true.
+ * of the added conditions to be true. The predicate inherits the query root: a join widens the root, + * admitting predicates that reference any entity in the query. * * @param predicate the predicate to add. * @return the predicate builder. */ -Methods named {@code and}/{@code or} are type-safe and restrict predicates to the root table's entity graph. - * Methods named {@code andAny}/{@code orAny} accept predicates from any table, including manually added joins.
+ *Predicates combine within the query root. A join widens the root, after which the {@link WhereBuilder} + * produces predicates that may reference any entity in the query; a path on an entity outside the query fails + * when the query is built.
* *{@code
@@ -51,24 +52,13 @@ public interface PredicateBuilder {
* Adds a predicate to the WHERE clause using an AND condition.
*
* This method combines the specified predicate with existing predicates using an AND operation, ensuring
- * that all added conditions must be true.
+ * that all added conditions must be true. The predicate inherits the query root: a join widens the root,
+ * admitting predicates that reference any entity in the query.
*
* @param predicate the predicate to add.
* @return the predicate builder.
*/
- PredicateBuilder and(@Nonnull PredicateBuilder predicate);
-
- /**
- * Adds a predicate to the WHERE clause using an AND condition.
- *
- * This method combines the specified predicate with existing predicates using an AND operation, ensuring
- * that all added conditions must be true.
- *
- * @param predicate the predicate to add.
- * @return the predicate builder.
- */
- PredicateBuilder andAny(@Nonnull PredicateBuilder predicate);
-
+ PredicateBuilder and(@Nonnull PredicateBuilder extends T, ?, ?> predicate);
/**
* Adds a predicate to the WHERE clause using an AND condition.
@@ -85,23 +75,13 @@ public interface PredicateBuilder {
* Adds a predicate to the WHERE clause using an OR condition.
*
* This method combines the specified predicate with existing predicates using an OR operation, allowing any
- * of the added conditions to be true.
- *
- * @param predicate the predicate to add.
- * @return the predicate builder.
- */
- PredicateBuilder or(@Nonnull PredicateBuilder predicate);
-
- /**
- * Adds a predicate to the WHERE clause using an OR condition.
- *
- * This method combines the specified predicate with existing predicates using an OR operation, allowing any
- * of the added conditions to be true.
+ * of the added conditions to be true. The predicate inherits the query root: a join widens the root,
+ * admitting predicates that reference any entity in the query.
*
* @param predicate the predicate to add.
* @return the predicate builder.
*/
- PredicateBuilder orAny(@Nonnull PredicateBuilder predicate);
+ PredicateBuilder or(@Nonnull PredicateBuilder extends T, ?, ?> predicate);
/**
* Adds a predicate to the WHERE clause using an OR condition.
diff --git a/storm-java21/src/main/java/st/orm/template/WhereBuilder.java b/storm-java21/src/main/java/st/orm/template/WhereBuilder.java
index 78aa3c689..2d242da44 100644
--- a/storm-java21/src/main/java/st/orm/template/WhereBuilder.java
+++ b/storm-java21/src/main/java/st/orm/template/WhereBuilder.java
@@ -33,8 +33,9 @@
* expressions. Each method returns a {@link PredicateBuilder} that can be further composed using
* {@code and()} and {@code or()} combinators.
*
- * Methods named {@code where} are type-safe and restrict metamodel paths to the root table's entity graph.
- * Methods named {@code whereAny} accept metamodel paths from any table, including manually added joins.
+ * The {@code where} methods are type-safe and restrict metamodel paths to the root table's entity graph.
+ * A join widens the query root, after which the {@code where} methods accept paths from any entity in the query;
+ * a path on an entity outside the query fails when the query is built.
*
* Example
* {@code
diff --git a/storm-java21/src/main/java/st/orm/template/impl/QueryBuilderImpl.java b/storm-java21/src/main/java/st/orm/template/impl/QueryBuilderImpl.java
index 0d8aa90e8..f34f9d648 100644
--- a/storm-java21/src/main/java/st/orm/template/impl/QueryBuilderImpl.java
+++ b/storm-java21/src/main/java/st/orm/template/impl/QueryBuilderImpl.java
@@ -461,13 +461,8 @@ static class PredicateBuilderImpl implements Predicate
}
@Override
- public PredicateBuilder and(@Nonnull PredicateBuilder predicate) {
- return new PredicateBuilderImpl<>(core.and(((PredicateBuilderImpl) predicate).core));
- }
-
- @Override
- public PredicateBuilder andAny(@Nonnull PredicateBuilder predicate) {
- return new PredicateBuilderImpl<>(core.andAny(((PredicateBuilderImpl) predicate).core));
+ public PredicateBuilder and(@Nonnull PredicateBuilder extends TX, ?, ?> predicate) {
+ return new PredicateBuilderImpl<>(core.and(((PredicateBuilderImpl extends TX, ?, ?>) predicate).core));
}
@Override
@@ -476,13 +471,8 @@ public PredicateBuilder and(@Nonnull StringTemplate template) {
}
@Override
- public PredicateBuilder or(@Nonnull PredicateBuilder predicate) {
- return new PredicateBuilderImpl<>(core.or(((PredicateBuilderImpl) predicate).core));
- }
-
- @Override
- public PredicateBuilder orAny(@Nonnull PredicateBuilder predicate) {
- return new PredicateBuilderImpl<>(core.orAny(((PredicateBuilderImpl) predicate).core));
+ public PredicateBuilder or(@Nonnull PredicateBuilder extends TX, ?, ?> predicate) {
+ return new PredicateBuilderImpl<>(core.or(((PredicateBuilderImpl extends TX, ?, ?>) predicate).core));
}
@Override
diff --git a/storm-java21/src/test/java/st/orm/template/QueryBuilderTest.java b/storm-java21/src/test/java/st/orm/template/QueryBuilderTest.java
index 2e500c4bb..4ad2c82d2 100644
--- a/storm-java21/src/test/java/st/orm/template/QueryBuilderTest.java
+++ b/storm-java21/src/test/java/st/orm/template/QueryBuilderTest.java
@@ -690,26 +690,49 @@ public void testHavingWithOrTemplate() {
assertEquals(3L, result.stream().mapToLong(Long::longValue).sum());
}
- // PredicateBuilder - andAny / orAny
+ // PredicateBuilder - and / or with a second predicate
@Test
- public void testPredicateAndAny() {
+ public void testPredicateAndPredicate() {
List cities = orm.entity(City.class).select()
.where(wb -> wb.where(RAW."\{City.class}.id = \{1}")
- .andAny(wb.where(RAW."\{City.class}.name = \{"Sun Paririe"}")))
+ .and(wb.where(RAW."\{City.class}.name = \{"Sun Paririe"}")))
.getResultList();
assertEquals(1, cities.size());
}
@Test
- public void testPredicateOrAny() {
+ public void testPredicateOrPredicate() {
List cities = orm.entity(City.class).select()
.where(wb -> wb.where(RAW."\{City.class}.id = \{999}")
- .orAny(wb.where(RAW."\{City.class}.name = \{"Madison"}")))
+ .or(wb.where(RAW."\{City.class}.name = \{"Madison"}")))
.getResultList();
assertEquals(1, cities.size());
}
+ // PredicateBuilder - and / or across entities on a widened builder
+
+ @Test
+ public void testPredicateAndAcrossEntitiesAfterJoin() {
+ List pets = orm.entity(Pet.class).select()
+ .innerJoin(Owner.class).on(Pet.class)
+ .where(wb -> wb.where(Pet_.name, EQUALS, "Leo")
+ .and(wb.where(Owner_.lastName, EQUALS, "Davis")))
+ .getResultList();
+ assertEquals(1, pets.size());
+ }
+
+ @Test
+ public void testPredicateOrAcrossEntitiesAfterJoin() {
+ // Pets named Leo (Betty Davis's pet) or owned by a Davis: Leo and Harold Davis's Iggy.
+ List pets = orm.entity(Pet.class).select()
+ .innerJoin(Owner.class).on(Pet.class)
+ .where(wb -> wb.where(Pet_.name, EQUALS, "Leo")
+ .or(wb.where(Owner_.lastName, EQUALS, "Davis")))
+ .getResultList();
+ assertEquals(2, pets.size());
+ }
+
@Test
public void testPredicateAndTemplate() {
List cities = orm.entity(City.class).select()
diff --git a/storm-kotlin/src/main/kotlin/st/orm/template/PredicateBuilder.kt b/storm-kotlin/src/main/kotlin/st/orm/template/PredicateBuilder.kt
index d87864c86..1da620c22 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/template/PredicateBuilder.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/template/PredicateBuilder.kt
@@ -20,24 +20,25 @@ import st.orm.Data
/**
* Represents a composable predicate for the WHERE clause of a query, supporting `AND` and `OR` composition.
*
- * `PredicateBuilder` instances are returned by the methods on [WhereBuilder] and can be combined using [and]
- * and [or] to build compound conditions. Each combinator returns a new `PredicateBuilder` that represents the
- * combined expression.
+ * `PredicateBuilder` instances are returned by the methods on [WhereBuilder] and by the infix operators such as
+ * `eq` and `like`. They combine with `and` and `or`; each combinator returns a new `PredicateBuilder` that
+ * represents the combined expression.
*
- * Methods named `and`/`or` are type-safe and restrict predicates to the root table's entity graph.
- * Methods named `andAny`/`orAny` accept predicates from any table, including manually added joins.
+ * Inside a [WhereBuilder] scope, the scope's `and`/`or` combinators inherit the query root: a narrow scope
+ * combines predicates within the root table's entity graph, and a join widens the root so the same syntax
+ * combines predicates across all entities in the query. Outside a scope, the top-level `and`/`or` extensions
+ * combine predicates that share the same root.
*
* ## Example
* ```kotlin
* val users = userRepository
* .select()
- * .where { predicate ->
- * predicate
- * .where(User_.active eq true)
- * .and(predicate.where(User_.email.isNotNull()))
- * .or(predicate.where(User_.role eq "admin"))
+ * .whereBuilder {
+ * (User_.active eq true)
+ * .and(User_.email.isNotNull())
+ * .or(User_.role eq "admin")
* }
- * .getResultList()
+ * .resultList
* ```
*
* @param T the type of the table being queried.
@@ -47,29 +48,6 @@ import st.orm.Data
* @see QueryBuilder
*/
public interface PredicateBuilder {
- /**
- * Adds a predicate to the WHERE clause using an AND condition.
- *
- * This method combines the specified predicate with existing predicates using an AND operation, ensuring
- * that all added conditions must be true.
- *
- * @param predicate the predicate to add.
- * @return the predicate builder.
- */
- public infix fun and(predicate: PredicateBuilder): PredicateBuilder
-
- /**
- * Adds a predicate to the WHERE clause using an AND condition.
- *
- *
- * This method combines the specified predicate with existing predicates using an AND operation, ensuring
- * that all added conditions must be true.
- *
- * @param predicate the predicate to add.
- * @return the predicate builder.
- */
- public infix fun andAny(predicate: PredicateBuilder): PredicateBuilder
-
/**
* Adds a predicate to the WHERE clause using an AND condition.
*
@@ -92,30 +70,6 @@ public interface PredicateBuilder {
*/
public infix fun and(template: TemplateString): PredicateBuilder
- /**
- * Adds a predicate to the WHERE clause using an OR condition.
- *
- *
- * This method combines the specified predicate with existing predicates using an OR operation, allowing any
- * of the added conditions to be true.
- *
- * @param predicate the predicate to add.
- * @return the predicate builder.
- */
- public infix fun or(predicate: PredicateBuilder): PredicateBuilder
-
- /**
- * Adds a predicate to the WHERE clause using an OR condition.
- *
- *
- * This method combines the specified predicate with existing predicates using an OR operation, allowing any
- * of the added conditions to be true.
- *
- * @param predicate the predicate to add.
- * @return the predicate builder.
- */
- public infix fun orAny(predicate: PredicateBuilder): PredicateBuilder
-
/**
* Adds a predicate to the WHERE clause using an OR condition.
*
diff --git a/storm-kotlin/src/main/kotlin/st/orm/template/QueryBuilder.kt b/storm-kotlin/src/main/kotlin/st/orm/template/QueryBuilder.kt
index 9d3cdb0c2..21a95489d 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/template/QueryBuilder.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/template/QueryBuilder.kt
@@ -25,6 +25,8 @@ import st.orm.core.template.impl.Elements.ObjectExpression
import st.orm.template.TemplateString.Companion.combine
import st.orm.template.TemplateString.Companion.raw
import st.orm.template.TemplateString.Companion.wrap
+import st.orm.template.impl.combineAnd
+import st.orm.template.impl.combineOr
import st.orm.template.impl.create
import st.orm.template.impl.createRef
import java.util.stream.Stream
@@ -48,7 +50,7 @@ import kotlin.reflect.KClass
* .where(User_.address.city.name eq "Sunnyvale")
* .orderBy(User_.email)
* .limit(10)
- * .getResultList()
+ * .resultList
* ```
*
* ## Example: Join with reified type arguments
@@ -56,7 +58,7 @@ import kotlin.reflect.KClass
* val users = userRepository
* .select()
* .innerJoin().on()
- * .getResultList()
+ * .resultList
* ```
*
* ## Example: Delete with WHERE clause
@@ -1369,6 +1371,28 @@ public fun Navigable.isNull(): PredicateBuilder = c
*/
public fun Navigable.isNotNull(): PredicateBuilder = create(this.asMetamodel(), IS_NOT_NULL, emptyList())
+/**
+ * Combines two predicates that share the same root using an AND condition.
+ *
+ * Inside a [WhereBuilder] scope, the scope's own `and` takes precedence and inherits the query root, so a widened
+ * query combines predicates across joined entities with the same syntax.
+ *
+ * @param predicate the predicate to add.
+ * @return the combined predicate builder.
+ */
+public infix fun PredicateBuilder.and(predicate: PredicateBuilder): PredicateBuilder = combineAnd(this, predicate)
+
+/**
+ * Combines two predicates that share the same root using an OR condition.
+ *
+ * Inside a [WhereBuilder] scope, the scope's own `or` takes precedence and inherits the query root, so a widened
+ * query combines predicates across joined entities with the same syntax.
+ *
+ * @param predicate the predicate to add.
+ * @return the combined predicate builder.
+ */
+public infix fun PredicateBuilder.or(predicate: PredicateBuilder): PredicateBuilder = combineOr(this, predicate)
+
// Block-based query DSL
/**
diff --git a/storm-kotlin/src/main/kotlin/st/orm/template/QueryTemplate.kt b/storm-kotlin/src/main/kotlin/st/orm/template/QueryTemplate.kt
index 7feab3a85..32224aecb 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/template/QueryTemplate.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/template/QueryTemplate.kt
@@ -38,7 +38,7 @@ import kotlin.reflect.KClass
* ```kotlin
* val users = orm.selectFrom(User::class)
* .where(User_.name eq "Alice")
- * .getResultList()
+ * .resultList
* ```
*
* @see ORMTemplate
diff --git a/storm-kotlin/src/main/kotlin/st/orm/template/TypedJoinBuilder.kt b/storm-kotlin/src/main/kotlin/st/orm/template/TypedJoinBuilder.kt
index 7ec5f3a7c..700978e16 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/template/TypedJoinBuilder.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/template/TypedJoinBuilder.kt
@@ -31,7 +31,7 @@ import kotlin.reflect.KClass
* val users = userRepository
* .select()
* .innerJoin(Order::class).on(User::class)
- * .getResultList()
+ * .resultList
* ```
*
* The same join can be expressed with reified type arguments:
@@ -39,7 +39,7 @@ import kotlin.reflect.KClass
* val users = userRepository
* .select()
* .innerJoin().on()
- * .getResultList()
+ * .resultList
* ```
*
* @param T the type of the table being queried.
diff --git a/storm-kotlin/src/main/kotlin/st/orm/template/WhereBuilder.kt b/storm-kotlin/src/main/kotlin/st/orm/template/WhereBuilder.kt
index 0ff9972fc..e131bccbe 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/template/WhereBuilder.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/template/WhereBuilder.kt
@@ -25,29 +25,28 @@ import st.orm.template.TemplateString.Companion.raw
/**
* A builder for constructing the WHERE clause of a query, providing type-safe predicate construction.
*
- * The `WhereBuilder` is passed to the lambda argument of [QueryBuilder.where] and offers methods for matching by
- * primary key, record, ref, metamodel path, or custom template string expressions. Each method returns a
- * [PredicateBuilder] that can be further composed using `and()` and `or()` combinators.
+ * The `WhereBuilder` is the receiver of the lambda passed to [QueryBuilder.whereBuilder] and offers methods for
+ * matching by primary key, record, ref, metamodel path, or custom template string expressions. Each method returns
+ * a [PredicateBuilder] that can be further composed using `and()` and `or()` combinators.
*
- * Methods named `where` are type-safe and restrict metamodel paths to the root table's entity graph.
- * Methods named `whereAny` accept metamodel paths from any table, including manually added joins.
+ * The `where` methods are type-safe and restrict metamodel paths to the root table's entity graph. A join widens
+ * the query root, and the scope's `and`/`or` combinators inherit it: a narrow scope combines predicates within
+ * the root graph, a widened scope combines predicates across all entities in the query.
*
* ## Example
* ```kotlin
* val users = userRepository
* .select()
- * .where { predicate ->
- * predicate
- * .where(User_.active eq true)
- * .and(predicate.where(User_.address.city.name eq "Sunnyvale"))
+ * .whereBuilder {
+ * (User_.active eq true) and (User_.address.city.name eq "Sunnyvale")
* }
- * .getResultList()
+ * .resultList
* ```
*
* @param T the type of the table being queried.
* @param R the type of the result.
* @param ID the type of the primary key.
- * @see QueryBuilder.where
+ * @see QueryBuilder.whereBuilder
* @see PredicateBuilder
*/
@SqlDsl
@@ -63,6 +62,30 @@ public interface WhereBuilder : SubqueryTemplate {
*/
public fun FALSE(): PredicateBuilder = where(raw("FALSE"))
+ /**
+ * Combines this predicate with another using an AND condition.
+ *
+ * The combinator inherits the query root of this `WhereBuilder`: a narrow scope combines predicates within
+ * the root table's entity graph, and a join widens the root so predicates rooted at any entity in the query
+ * combine with the same syntax.
+ *
+ * @param predicate the predicate to add.
+ * @return the combined predicate builder.
+ */
+ public infix fun PredicateBuilder.and(predicate: PredicateBuilder): PredicateBuilder
+
+ /**
+ * Combines this predicate with another using an OR condition.
+ *
+ * The combinator inherits the query root of this `WhereBuilder`: a narrow scope combines predicates within
+ * the root table's entity graph, and a join widens the root so predicates rooted at any entity in the query
+ * combine with the same syntax.
+ *
+ * @param predicate the predicate to add.
+ * @return the combined predicate builder.
+ */
+ public infix fun PredicateBuilder.or(predicate: PredicateBuilder): PredicateBuilder
+
/**
* Adds an `EXISTS` condition to the WHERE clause using the specified subquery.
*
diff --git a/storm-kotlin/src/main/kotlin/st/orm/template/impl/PredicateBuilderFactory.kt b/storm-kotlin/src/main/kotlin/st/orm/template/impl/PredicateBuilderFactory.kt
index cdc11bd8a..7893b251a 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/template/impl/PredicateBuilderFactory.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/template/impl/PredicateBuilderFactory.kt
@@ -92,3 +92,27 @@ internal fun createRefWithId(
operator: Operator,
o: Iterable>,
): PredicateBuilder = PredicateBuilderImpl(PredicateBuilderFactory.createRefWithId(path, operator, o))
+
+/**
+ * Combines two predicates using an AND condition, keeping the left-hand predicate's root.
+ *
+ * @param left the predicate to combine into
+ * @param right the predicate to add
+ * @return the combined [PredicateBuilder]
+ */
+internal fun combineAnd(
+ left: PredicateBuilder,
+ right: PredicateBuilder,
+): PredicateBuilder = PredicateBuilderImpl((left as PredicateBuilderImpl).core.and((right as PredicateBuilderImpl).core))
+
+/**
+ * Combines two predicates using an OR condition, keeping the left-hand predicate's root.
+ *
+ * @param left the predicate to combine into
+ * @param right the predicate to add
+ * @return the combined [PredicateBuilder]
+ */
+internal fun combineOr(
+ left: PredicateBuilder,
+ right: PredicateBuilder,
+): PredicateBuilder = PredicateBuilderImpl((left as PredicateBuilderImpl).core.or((right as PredicateBuilderImpl).core))
diff --git a/storm-kotlin/src/main/kotlin/st/orm/template/impl/QueryBuilderImpl.kt b/storm-kotlin/src/main/kotlin/st/orm/template/impl/QueryBuilderImpl.kt
index b276afe40..a287bc119 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/template/impl/QueryBuilderImpl.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/template/impl/QueryBuilderImpl.kt
@@ -324,16 +324,8 @@ internal class QueryBuilderImpl(
internal class PredicateBuilderImpl(
val core: st.orm.core.template.PredicateBuilder,
) : PredicateBuilder {
- override infix fun and(predicate: PredicateBuilder): PredicateBuilder = PredicateBuilderImpl(core.and((predicate as PredicateBuilderImpl).core))
-
- override fun andAny(predicate: PredicateBuilder): PredicateBuilder = PredicateBuilderImpl(core.andAny((predicate as PredicateBuilderImpl).core))
-
override fun and(template: TemplateString): PredicateBuilder = PredicateBuilderImpl(core.and(template.unwrap))
- override infix fun or(predicate: PredicateBuilder): PredicateBuilder = PredicateBuilderImpl(core.or((predicate as PredicateBuilderImpl).core))
-
- override fun orAny(predicate: PredicateBuilder): PredicateBuilder = PredicateBuilderImpl(core.orAny((predicate as PredicateBuilderImpl).core))
-
override fun or(template: TemplateString): PredicateBuilder = PredicateBuilderImpl(core.or(template.unwrap))
}
@@ -375,6 +367,10 @@ internal class QueryBuilderImpl(
): PredicateBuilder = PredicateBuilderImpl(core.where(path.asMetamodel(), operator, it))
override fun where(template: TemplateString): PredicateBuilder = PredicateBuilderImpl(core.where((template as TemplateStringHolder).templateString))
+
+ override infix fun PredicateBuilder.and(predicate: PredicateBuilder): PredicateBuilder = PredicateBuilderImpl((this as PredicateBuilderImpl).core.and((predicate as PredicateBuilderImpl).core))
+
+ override infix fun PredicateBuilder.or(predicate: PredicateBuilder): PredicateBuilder = PredicateBuilderImpl((this as PredicateBuilderImpl).core.or((predicate as PredicateBuilderImpl).core))
}
/**
diff --git a/storm-kotlin/src/test/kotlin/st/orm/template/EntityRepositoryTest.kt b/storm-kotlin/src/test/kotlin/st/orm/template/EntityRepositoryTest.kt
index 5d8e01bf1..bbd7753f0 100644
--- a/storm-kotlin/src/test/kotlin/st/orm/template/EntityRepositoryTest.kt
+++ b/storm-kotlin/src/test/kotlin/st/orm/template/EntityRepositoryTest.kt
@@ -1033,10 +1033,10 @@ open class EntityRepositoryTest(
preparedQuery.close()
}
- // QueryBuilder: whereAny with multiple predicates
+ // QueryBuilder: whereBuilder with multiple predicates
@Test
- fun `whereAny with combined predicates should match any`() {
+ fun `whereBuilder with combined predicates should match any`() {
val repo = orm.entity(City::class)
val namePath = metamodel(repo.model, "name")
val cities = repo.select().whereBuilder {
@@ -1045,15 +1045,15 @@ open class EntityRepositoryTest(
cities shouldHaveSize 2
}
- // QueryBuilder: andAny / orAny predicate builders
+ // QueryBuilder: and / or predicate combinators
@Test
- fun `andAny should combine predicates with AND-OR logic`() {
+ fun `and should combine predicates with AND-OR logic`() {
val repo = orm.entity(Owner::class)
val firstNamePath = metamodel(repo.model, "first_name")
val lastNamePath = metamodel(repo.model, "last_name")
val result = repo.select().whereBuilder {
- (lastNamePath eq "Davis") andAny (
+ (lastNamePath eq "Davis") and (
(firstNamePath eq "Betty") or (firstNamePath eq "Harold")
)
}.resultList
@@ -1061,7 +1061,7 @@ open class EntityRepositoryTest(
}
@Test
- fun `orAny should combine predicates with OR logic`() {
+ fun `or should combine predicates with OR logic`() {
val repo = orm.entity(City::class)
val namePath = metamodel(repo.model, "name")
val result = repo.select().whereBuilder {
diff --git a/storm-kotlin/src/test/kotlin/st/orm/template/ORMTemplateTest.kt b/storm-kotlin/src/test/kotlin/st/orm/template/ORMTemplateTest.kt
index 0d63463e4..ec9be0096 100644
--- a/storm-kotlin/src/test/kotlin/st/orm/template/ORMTemplateTest.kt
+++ b/storm-kotlin/src/test/kotlin/st/orm/template/ORMTemplateTest.kt
@@ -1540,26 +1540,26 @@ open class ORMTemplateTest(
cities shouldHaveSize 0
}
- // PredicateBuilder: andAny/orAny
+ // PredicateBuilder: and/or
@Test
- fun `predicateBuilder andAny should combine predicates from different entities`() {
+ fun `predicateBuilder and should combine predicates`() {
val repo = orm.entity(City::class)
val namePath = metamodel(repo.model, "name")
val idPath = metamodel(repo.model, "id")
val cities = repo.select().whereBuilder {
- (namePath eq "Madison") andAny (idPath eq 2)
+ (namePath eq "Madison") and (idPath eq 2)
}.resultList
cities shouldHaveSize 1
}
@Test
- fun `predicateBuilder orAny should combine predicates from different entities`() {
+ fun `predicateBuilder or should combine predicates`() {
val repo = orm.entity(City::class)
val namePath = metamodel(repo.model, "name")
val idPath = metamodel(repo.model, "id")
val cities = repo.select().whereBuilder {
- (namePath eq "Madison") orAny (idPath eq 1)
+ (namePath eq "Madison") or (idPath eq 1)
}.resultList
cities shouldHaveSize 2
}
diff --git a/storm-kotlin/src/test/kotlin/st/orm/template/QueryBuilderTest.kt b/storm-kotlin/src/test/kotlin/st/orm/template/QueryBuilderTest.kt
index 7863750f5..24121c053 100644
--- a/storm-kotlin/src/test/kotlin/st/orm/template/QueryBuilderTest.kt
+++ b/storm-kotlin/src/test/kotlin/st/orm/template/QueryBuilderTest.kt
@@ -1243,28 +1243,53 @@ open class QueryBuilderTest(
}
@Test
- fun `andAny should combine predicates across different types`() {
+ fun `and should combine two predicates`() {
val repo = orm.entity(Owner::class)
val firstNamePath = metamodel(repo.model, "first_name")
val lastNamePath = metamodel(repo.model, "last_name")
- // Use andAny to combine a predicate with another typed predicate.
val result = repo.select().whereBuilder {
- (firstNamePath eq "Betty") andAny (lastNamePath eq "Davis")
+ (firstNamePath eq "Betty") and (lastNamePath eq "Davis")
}.resultList
result shouldHaveSize 1
result[0].firstName shouldBe "Betty"
}
@Test
- fun `orAny should combine predicates across different types`() {
+ fun `or should combine two predicates`() {
val repo = orm.entity(City::class)
val namePath = metamodel(repo.model, "name")
val result = repo.select().whereBuilder {
- (namePath eq "Madison") orAny (namePath eq "Windsor")
+ (namePath eq "Madison") or (namePath eq "Windsor")
}.resultList
result shouldHaveSize 2
}
+ @Test
+ fun `and should combine predicates across entities after a join`() {
+ val petNamePath = metamodel(orm.entity(Pet::class).model, "name")
+ val ownerLastNamePath = metamodel(orm.entity(Owner::class).model, "last_name")
+ val pets = orm.entity(Pet::class).select()
+ .innerJoin(Owner::class).on(Pet::class)
+ .whereBuilder {
+ (petNamePath eq "Leo") and (ownerLastNamePath eq "Davis")
+ }.resultList
+ pets shouldHaveSize 1
+ pets[0].name shouldBe "Leo"
+ }
+
+ @Test
+ fun `or should combine predicates across entities after a join`() {
+ // Pets named Leo (Betty Davis's pet) or owned by a Davis: Leo and Harold Davis's Iggy.
+ val petNamePath = metamodel(orm.entity(Pet::class).model, "name")
+ val ownerLastNamePath = metamodel(orm.entity(Owner::class).model, "last_name")
+ val pets = orm.entity(Pet::class).select()
+ .innerJoin(Owner::class).on(Pet::class)
+ .whereBuilder {
+ (petNamePath eq "Leo") or (ownerLastNamePath eq "Davis")
+ }.resultList
+ pets shouldHaveSize 2
+ }
+
@Test
fun `predicate and with TemplateBuilder should combine conditions`() {
val repo = orm.entity(City::class)