Summary
Add a @Supplied property annotation (and a corresponding SuppliedProperty<T> return type) that decomposes a derived property into (Criteria, Function<Result, T>). The Runway pipeline collects all such properties across the records being loaded, batches their criteria into a single command-group submit alongside the load, and projects results back into each record's data map before framing.
@Supplied is a complement to @Computed, not a replacement. @Computed stays for methods that do composite work (multiple queries, branching logic, pure in-memory computation). @Supplied covers the common pattern of "this property is the projection of a single criteria query against the database."
The pattern this addresses
Models frequently expose properties that are conceptually "a derived value that requires one criteria query." Today they're written as @Computed:
@Computed
public Set<Sibling> siblings() {
return computeOnce("siblings",
() -> db.find(Sibling.class, /* criteria using this.id */));
}
These fire one wire call each, sequentially, during framing — even when the framer is processing many records that could all have their criteria batched into one round trip. @Computed runs late in the lifecycle (when data() is iterated or get(key) is called) and is opaque to Runway, so there is no way to anticipate or batch the queries.
The result is N sequential round trips for what is structurally one batched lookup.
Proposed API
@Supplied
public SuppliedProperty<Set<Sibling>> siblings() {
return SuppliedProperty.of(
Sibling.class,
Criteria.where().key("parent").operator(Operator.LINKS_TO).value(id()),
result -> result // result is Set<Sibling>; projection is identity here
);
}
Where SuppliedProperty<T> is:
public final class SuppliedProperty<T> {
private final Class<? extends Record> resultClass;
private final Criteria criteria;
private final Function<?, T> projection;
public static <T, R extends Record> SuppliedProperty<T> of(
Class<R> resultClass,
Criteria criteria,
Function<Set<R>, T> projection) { ... }
public static <T, R extends Record> SuppliedProperty<T> ofUnique(
Class<R> resultClass,
Criteria criteria,
Function<R, T> projection) { ... }
}
The method body returns a description of how to compute the value, not the value itself. Runway resolves the description in a batched phase.
Execution model
- Phase 1 (existing): load.
BatchReader.submit() runs navigate + cleanup BFS as today, returning record IDs and their data.
- Phase 2 (new): collect. Runway walks every loaded record's
@Supplied methods, invokes them to obtain their SuppliedProperty descriptors, and groups all the criteria by result class into a single follow-on BatchReader.submit().
- Phase 3 (new): project. When phase 2's submit returns, each
SuppliedProperty projects its slice of the result set into a concrete value and stashes it on the record (e.g., into the computeOnce cache or a parallel _supplied cache).
- Phase 4 (existing): frame. Framing reads the pre-resolved values. No wire calls during framing for
@Supplied properties.
For a Conversation with R root Exchanges where each root exposes isMultivariate() and siblings() as @Supplied, the cost goes from 2R sequential findUnique calls to 1 batched submit.
Constraints and semantics
@Supplied criteria must not depend on values produced by other @Supplied methods on the same record. Phase 2 is one batch; chained dependencies would require sequencing. Allowed inputs: this.id(), declared fields, fields populated by phase 1 (navigate). Disallowed: another @Supplied value on the same record.
- Result is cached per (record instance, method name), same convention as
computeOnce.
- Falls back gracefully on older Concourse servers. When
supportsBulkCommands is false, Runway can execute @Supplied criteria sequentially using the same IncrementalReader path as legacy @Computed. Same correctness, no batching benefit.
- Methods can mix. A model can have both
@Supplied and @Computed methods; framing handles them as a unit (suppliers resolved first, computed evaluated later if iterated).
Where this fits in the framing pipeline
The collection phase fits naturally between load and frame. In Runway.java's selectOne / selectMany paths, after the load completes and before the result is returned to the caller, walk the loaded records' classes for @Supplied methods, build the batch, submit, project.
Record.computeOnce provides a usable storage mechanism; an alternative is a dedicated _supplied cache parallel to _audit.
Concrete consumer
cinchapi/relay-server has two @Computed methods on Exchange that fit this exactly:
isMultivariate() → db.findUnique(Conversation, where root LINKS_TO this.id) and a findAny().isPresent() projection.
siblings() → same findUnique, different projection (the conversation's root minus this).
Both would become @Supplied and fold into the same batched submit as the Conversation load.
Across the broader application surface, any model method that's currently @Computed and consists of a single db.find or db.findUnique + projection is a candidate.
Out of scope
- A
@Supplied variant whose criteria can reference another @Supplied result. Could be added later via an explicit dependency declaration if the use case appears.
- Field-level supplied annotations (only methods for now).
- A unified replacement for
@Computed. The two coexist.
Summary
Add a
@Suppliedproperty annotation (and a correspondingSuppliedProperty<T>return type) that decomposes a derived property into(Criteria, Function<Result, T>). The Runway pipeline collects all such properties across the records being loaded, batches their criteria into a single command-group submit alongside the load, and projects results back into each record's data map before framing.@Suppliedis a complement to@Computed, not a replacement.@Computedstays for methods that do composite work (multiple queries, branching logic, pure in-memory computation).@Suppliedcovers the common pattern of "this property is the projection of a single criteria query against the database."The pattern this addresses
Models frequently expose properties that are conceptually "a derived value that requires one criteria query." Today they're written as
@Computed:These fire one wire call each, sequentially, during framing — even when the framer is processing many records that could all have their criteria batched into one round trip.
@Computedruns late in the lifecycle (whendata()is iterated orget(key)is called) and is opaque to Runway, so there is no way to anticipate or batch the queries.The result is N sequential round trips for what is structurally one batched lookup.
Proposed API
Where
SuppliedProperty<T>is:The method body returns a description of how to compute the value, not the value itself. Runway resolves the description in a batched phase.
Execution model
BatchReader.submit()runs navigate + cleanup BFS as today, returning record IDs and their data.@Suppliedmethods, invokes them to obtain theirSuppliedPropertydescriptors, and groups all the criteria by result class into a single follow-onBatchReader.submit().SuppliedPropertyprojects its slice of the result set into a concrete value and stashes it on the record (e.g., into thecomputeOncecache or a parallel_suppliedcache).@Suppliedproperties.For a Conversation with R root Exchanges where each root exposes
isMultivariate()andsiblings()as@Supplied, the cost goes from2R sequential findUnique callsto1 batched submit.Constraints and semantics
@Suppliedcriteria must not depend on values produced by other@Suppliedmethods on the same record. Phase 2 is one batch; chained dependencies would require sequencing. Allowed inputs:this.id(), declared fields, fields populated by phase 1 (navigate). Disallowed: another@Suppliedvalue on the same record.computeOnce.supportsBulkCommandsis false, Runway can execute@Suppliedcriteria sequentially using the sameIncrementalReaderpath as legacy@Computed. Same correctness, no batching benefit.@Suppliedand@Computedmethods; framing handles them as a unit (suppliers resolved first, computed evaluated later if iterated).Where this fits in the framing pipeline
The collection phase fits naturally between
loadandframe. InRunway.java'sselectOne/selectManypaths, after the load completes and before the result is returned to the caller, walk the loaded records' classes for@Suppliedmethods, build the batch, submit, project.Record.computeOnceprovides a usable storage mechanism; an alternative is a dedicated_suppliedcache parallel to_audit.Concrete consumer
cinchapi/relay-server has two
@Computedmethods onExchangethat fit this exactly:isMultivariate()→db.findUnique(Conversation, where root LINKS_TO this.id)and afindAny().isPresent()projection.siblings()→ samefindUnique, different projection (the conversation'srootminusthis).Both would become
@Suppliedand fold into the same batched submit as the Conversation load.Across the broader application surface, any model method that's currently
@Computedand consists of a singledb.findordb.findUnique+ projection is a candidate.Out of scope
@Suppliedvariant whose criteria can reference another@Suppliedresult. Could be added later via an explicit dependency declaration if the use case appears.@Computed. The two coexist.