Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import st.orm.Discriminator.DiscriminatorType;
import st.orm.Element;
import st.orm.Metamodel;
import st.orm.Navigable;
import st.orm.Operator;
import st.orm.Ref;
import st.orm.SelectMode;
Expand Down Expand Up @@ -612,12 +613,23 @@ private Object resolveElements(@Nullable Object value) throws SqlTemplateExcepti
case TemplateString ignore -> throw new SqlTemplateException("TemplateString is not allowed as a string template value.");
case Stream<?> ignore -> throw new SqlTemplateException("Stream is not supported as a string template value. Collect the Stream into a List before passing it.");
case Subqueryable t -> new Subquery(t.getSubquery(), true);
case Metamodel<?, ?> m when m.isColumn() -> new st.orm.core.template.impl.Elements.Column(m, CASCADE);
case Metamodel<?, ?> ignore -> throw new SqlTemplateException("Metamodel does not reference a column. Use a column-level metamodel (e.g., User_.name) rather than a table-level metamodel.");
case Navigable<?, ?> m when m.isColumn() -> new Elements.Column(toColumnMetamodel(m), CASCADE);
case Navigable<?, ?> ignore -> throw new SqlTemplateException("Path does not reference a column. Use a column-level path (e.g., User_.name) rather than a table-level path.");
case null, default -> value;
};
}

/**
* Resolves a navigable used as a template value into a column metamodel. Full metamodels are used as-is; a
* navigation-only node (one that navigates beyond a {@link Ref}) is rebuilt into a resolvable metamodel for its
* path so it can be selected or filtered. The rebuilt metamodel is query-only and cannot extract a value.
*/
private static <T extends Data> Metamodel<T, ?> toColumnMetamodel(@Nonnull Navigable<T, ?> navigable) {
return navigable instanceof Metamodel<T, ?> metamodel
? metamodel
: Metamodel.of(navigable.root(), navigable.fieldPath());
}

/**
* Resolves the {@link Model} instance corresponding to the given metamodel.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,10 @@ private void collectReferencedTablePaths(@Nonnull Class<? extends Data> rootTabl
for (var value : template.values()) {
switch (value) {
case Metamodel<?, ?> metamodel -> addReferencedTablePath(rootTable, metamodel, paths);
// A navigation-only node (one that navigates beyond a Ref) is rebuilt into a resolvable metamodel,
// so the joins beyond the reference are derived for it.
case Navigable<?, ?> navigable when navigable.isColumn() ->
addReferencedTablePath(rootTable, toColumnMetamodel(navigable), paths);
case Expression expression ->
collectReferencedTablePaths(rootTable, expression, paths, tables, hydratedTables, hydratedPaths);
case Element element -> collectReferencedTablePaths(rootTable, element, paths, tables, hydratedTables, hydratedPaths);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ public void testTypedMetamodelFilterThroughRef() {
assertEquals(viaEntity, viaTypedRef);
}

@Test
public void testFilterThroughRefInWhereTemplate() {
var orm = ORMTemplate.of(dataSource);
// A navigation-only node interpolated into a where template fragment must resolve to a column with its
// joins, exactly like the predicate form, rather than degrade to a bind parameter.
List<Integer> viaPredicate = orm.entity(PetOwnerRef.class).select()
.where(PetOwnerRef_.owner.address.city.name, EQUALS, "Madison")
.getResultList().stream().map(PetOwnerRef::id).sorted().toList();
List<Integer> viaTemplate = orm.entity(PetOwnerRef.class).select()
.where(raw("\0 = \0", PetOwnerRef_.owner.address.city.name, "Madison"))
.getResultList().stream().map(PetOwnerRef::id).sorted().toList();
assertFalse(viaPredicate.isEmpty());
assertEquals(viaPredicate, viaTemplate);
}

@Test
public void testBeyondRefNodeIsNavigableOnly() {
// The reference node itself is a value metamodel (getValue returns the Ref, so getResultGroupedByRef works),
Expand Down
Loading