← Back to Stormify Documentation

PagedQuery

class PagedQuery<T : Any>

Stateless, thread-safe paginated query executor for entities of type T, designed for server-side use (REST, RPC, gRPC, any stateless request/response context).

A query is created by a Stormify instance and works on it: stormify.pagedQuery<T>(), or pagedQuery<T>() on the instance of the enclosing transaction or the default instance; from Java, stormify.pagedQuery(T.class).

A PagedQuery holds only column definitions and fixed constraints. It does not hold per-request mutable state (no filter/sort on Facet, no cached page, no selected entity, no kotlin.collections.AbstractList surface). All dynamic state flows per request through execute via a PageSpec; the result of each call is a fresh Page.

Configure a PagedQuery once (typically at application startup, per endpoint / grid) and share the instance across arbitrarily many concurrent execute calls.

When to use PagedQuery vs PagedList

  • Use PagedQuery when the caller is stateless (REST handler, RPC method, background job that processes spec'd queries). Facet aliases are the only identifiers that cross the boundary — no schema, no SQL, no paths.

  • Use PagedList when you're driving a stateful UI grid that keeps selection, lazy page cache, and per-column filter mutation across user interactions.

Usage

// One-time setup, ideally at application startup:
val customers = stormify.pagedQuery<Customer>().apply {
addFacet("search", "name", "email", "city").also {
it.isSortable = false // search is filter-only
}
addFacet("name", "name") // filterable + sortable
addFacet("city", "city")
}

// Per request:
val page: Page<Customer> = customers.execute(
PageSpec(
filters = mapOf("search" to "acme"),
sorts = mapOf("name" to SortState.ASCENDING),
page = 0, pageSize = 25,
)
)

Properties

Link copied to clipboard

The entity class whose rows this query paginates.

Link copied to clipboard

The columns defined on this query. Read-only view; add columns via addFacet / addSqlFacet.

Link copied to clipboard

Whether the query should return only distinct rows. Set at configuration time. Like every PagedQuery setting, intended to be assigned once at startup and not mutated while requests are in flight.

Link copied to clipboard

Direction of the primary key in the generated ORDER BY. The key orders the rows when the spec has no sort, and closes every ORDER BY as the tie-breaker after the requested sorts. Default SortState.ASCENDING. Set at configuration time, like isDistinct.

Functions

Link copied to clipboard
fun addFacet(alias: String, vararg fieldPaths: String): Facet

Adds a filter/sort column under alias with one or more field paths. The column type is auto-detected from the first path's Kotlin type. Multiple paths use OR logic during filtering (e.g., a single "search" column that matches any of several fields).

fun addFacet(alias: String, vararg paths: ScalarPath): Facet

Adds a column using type-safe KSP-generated path objects.

fun addFacet(alias: String, enumValues: Map<String, Any>, vararg fieldPaths: String): Facet

Enum-column variant of addFacet with an explicit display→DB value map.

fun addFacet(alias: String, enumValues: Map<String, Any>, vararg paths: ScalarPath): Facet

Enum-column typed-path variant.

fun addFacet(alias: String, type: Facet.Type, vararg fieldPaths: String): Facet

Explicit-type variant of addFacet.

fun addFacet(alias: String, type: Facet.Type, vararg paths: ScalarPath): Facet

Explicit-type typed-path variant.

Link copied to clipboard
fun addSqlFacet(alias: String, expression: String, type: Facet.Type = Facet.Type.TEXT): Facet

Adds a raw/custom column backed by an arbitrary SQL expression (e.g. "SUM(amount)", "COALESCE(a, b)"). The expression is emitted verbatim, so it is the configurator's responsibility to make it safe and dialect-compatible — it is never derived from user input.

fun addSqlFacet(alias: String, expression: String, type: Facet.Type, converter: Converter): Facet

Raw column with a custom Converter for filter semantics.

Link copied to clipboard

Registers a TableRef for the root entity table. The ref's TableRef.alias resolves to the underlying DB table name, so raw SQL in setConstraints or in an addSqlFacet expression can safely reference the root without hardcoding the table name.

Registers a TableRef for a table reached through the given dotted path (e.g. "address", "company.hq"). The final segment must be an FK reference, not a scalar field. The associated JOIN is activated in every subsequent SQL build while the ref's TableRef.isActive is true.

Registers a TableRef from a KSP-generated typed ReferencePath.

Link copied to clipboard
fun constraints(query: String, vararg args: Any?): PagedQuery<T>
Link copied to clipboard

Fluent isDistinct: turns distinct mode on.

Link copied to clipboard
fun execute(spec: PageSpec): Page<T>

Executes a single page query against the configured columns and the caller-supplied spec. Thread-safe — multiple concurrent calls on the same PagedQuery instance serialize only the (cheap) SQL-plan stage and run the database roundtrip in parallel.

Link copied to clipboard
fun facet(alias: String, vararg fieldPaths: String): PagedQuery<T>

Fluent addFacet using dot-notation paths.

fun facet(alias: String, vararg paths: ScalarPath): PagedQuery<T>

Fluent addFacet: one facet under alias over paths, OR-combined when more than one.

fun facet(alias: String, enumValues: Map<String, Any>, vararg fieldPaths: String): PagedQuery<T>

Fluent addFacet for an ENUM facet, using dot-notation paths.

fun facet(alias: String, enumValues: Map<String, Any>, vararg paths: ScalarPath): PagedQuery<T>

Fluent addFacet for an ENUM facet with a display-name-to-DB-value map.

fun facet(alias: String, type: Facet.Type, vararg fieldPaths: String): PagedQuery<T>

Fluent addFacet with an explicit type, using dot-notation paths.

fun facet(alias: String, type: Facet.Type, vararg paths: ScalarPath): PagedQuery<T>

Fluent addFacet with an explicit type.

Link copied to clipboard
fun filterValues(alias: String, spec: PageSpec): Page<String>

Returns one page of distinct values for the facet registered under alias, filtered by spec's other filters (the facet's own filter, if any, is ignored — so pickers can display every value that would become available once selected).

Link copied to clipboard

Returns one page of distinct values for alias together with their row counts — facet-picker style. Same filter semantics as filterValues: the facet's own filter is excluded.

Link copied to clipboard
fun forEachStreaming(spec: PageSpec, action: RowConsumer<T>)

Streams every row matching the filters/sorts/constraints in spec through action via a database cursor. Uses the same chunked sibling-batching as PagedList.forEachStreaming: FK touches inside action resolve in batches of onl.ycode.stormify.SiblingGroup.DEFAULT_BATCH_SIZE, not one-per-row.

Link copied to clipboard

Returns an aggregation builder bound to this query and the filters/ constraints derived from spec. Page/pageSize are ignored — aggregations always consider the entire matching set.

Link copied to clipboard
fun getFacet(alias: String): Facet

Returns the column registered under alias, or throws if no such alias.

Link copied to clipboard
fun keySort(direction: SortState): PagedQuery<T>

Fluent keySort: sets the direction of the primary key in the ORDER BY.

Link copied to clipboard
fun setConstraints(query: String, vararg args: Any?)

Sets a fixed constraint (baseline WHERE clause) that is always applied in addition to any per-request filters. Use for server-enforced narrowing that must not be overrideable by the client (e.g., "tenant_id = ?").