Skip to content

EQL Anti‐Patterns

Vlad Bilyk edited this page Jul 14, 2026 · 1 revision

1. yieldAll from a source query of an entity type with calculated properties

This EQL anti-pattern involves calling yieldAll() on a query whose source is another query (a source query) of an entity result type that declares calculated properties. It looks harmless — "just give me everything back" — but it may fail at query-compilation time with Cannot resolve property [...].

The trap is subtle because of an asymmetry in how EQL treats a source query of an entity result type: it exposes all of the entity's calculated properties for use in the enclosing query, but it exposes only the persistent properties that the source query actually yielded. So yieldAll() dutifully tries to yield a calculated property whose underlying columns were never carried through — and the calculated property's expression has nothing to resolve against.

The anti-pattern

Consider a Vehicle entity that references the vehicle which superseded it (replacedBy), carries two monetary properties, and derives a total from them with a calculated property.

@KeyType(String.class)
@MapEntityTo
public class Vehicle extends AbstractEntity<String> {

    @IsProperty
    @MapTo
    private Vehicle replacedBy;

    @IsProperty
    @MapTo
    private Money listPrice;

    @IsProperty
    @MapTo
    private Money purchasePrice;

    @IsProperty
    @Calculated("listPrice.amount + purchasePrice.amount")
    private BigDecimal totalCost;

}

Now suppose we build a source query yields only some of the persistent properties, and then wrap it in an enclosing query that pulls "everything" back out with yieldAll().

var sourceQuery = select(Vehicle.class)
        .yield().prop("id").as("id")
        .yield().prop("replacedBy").as("replacedBy")
        .modelAsEntity(Vehicle.class);

// Select from the source query and yield everything.
var query = select(replacements)
        .yieldAll()
        .modelAsEntity(Vehicle.class);

co(Vehicle.class).getAllEntities(from(query).model()); // fails

Note what the source query yields: id and replacedBy — and nothing else. In particular, it does not yield listPrice or purchasePrice.

What happens

Executing query fails with Cannot resolve property [listPrice.amount].

Crucially, this is a compile-time failure, raised while EQL processes the query model — before any SQL is generated or sent to the database. It is not a data problem; the query is malformed and can never run.

Why it happens

yieldAll() does not mean "the columns this source query yields". It means "all yielded properties + all calculated properties of the result type". For a Vehicle-typed source, that set includes totalCost, which depends on listPrice and purchasePrice. Because these persistent properties are not yielded by the source query, compilation fails.

Clone this wiki locally