← Back to Stormify Documentation

AbstractPagedQuery

Abstract base class for PagedQuery — a stateless, thread-safe query executor designed for server-side use (REST, RPC, gRPC, any stateless request/response context). Users do not instantiate this directly; use the platform-specific PagedQuery subclass instead.

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 = 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 SortDir.ASC),
page = 0, pageSize = 25,
)
)

Inheritors

PagedQuery

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.

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
open override fun attachTo(stormify: Stormify)

Binds stormify to this object. Implementations store the reference and may reset any state that depends on the previously-attached instance.

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 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: (T) -> Unit)

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 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 = ?").