Package-level declarations

Types

Link copied to clipboard

A QueryBackendFactory that creates each aggregate's binding once and reuses it.

Link copied to clipboard
abstract class AbstractQueryGateway<R : Any>(val namedAggregate: NamedAggregate, backend: QueryBackend, schemaProvider: QueryModelSchemaProvider, targetType: JavaType, filters: List<QueryFilter>, filterType: KClass<*>, policies: List<QueryPolicy>, observer: QueryObserver, val entryPolicy: QueryEntryPolicy = QueryEntryPolicy.DEFAULT) : QueryGateway<R>
Link copied to clipboard
class AdmittedQuery<out Q : Any>

A query that admission let through for one subscription, with the entry it ran under and the resolution of every field reference it carries. Only QueryAdmission creates it, so a QueryBackend cannot receive a query that skipped admission.

Link copied to clipboard
class BackendPage(val rows: List<ObjectNode>, val total: Long? = null, val positions: List<CursorPosition>? = null)

One window of records. total is present when the window asked for it; positions holds each row's native cursor position, in row order, for a PageWindow.Keyset.

Link copied to clipboard

Several observers behind one: each callback reaches every delegate, a failing delegate is logged and does not stop the others, and audits are built when any delegate wants them.

Link copied to clipboard
class CursorPosition(values: List<Any?>)

A record's position in a cursor sort: one native value per sort field, in sort order, exactly as the storage orders them (BSON values for MongoDB, hit.sort() values for Elasticsearch). Only the backend that produced it interprets the values.

Link copied to clipboard

The backend half of a cursor token: native position values to bytes and back. decode throws on any payload it did not produce for a sort of size fields; the core reports every such failure as the invalid cursor error.

Link copied to clipboard
class FilterNormalizer(clock: Clock = Clock.systemDefaultZone(), defaultZoneId: ZoneId = ZoneId.systemDefault())
Link copied to clipboard
sealed interface GroupWindow

Which groups QueryBackend.aggregate returns.

Link copied to clipboard
sealed interface PageWindow

Which records QueryBackend.page returns.

Link copied to clipboard

How a backend hands a runtime POJO operand to its driver.

Link copied to clipboard
class QueryAdmission(namedAggregate: NamedAggregate, filters: List<QueryFilter> = emptyList(), policies: List<QueryPolicy> = emptyList(), entryPolicy: QueryEntryPolicy = QueryEntryPolicy.DEFAULT)

The one admission pipeline of an aggregate's queries: it turns what a caller submitted into the AdmittedQuery a QueryBackend may execute, in the fixed order of the design (§5.4):

Link copied to clipboard

One query as an audit trail sees it: who asked (read the principal from context; Wow does not own identity), what shape of query on which model version, under which restrictions, and what came back. It never carries a filter value, so no personal data from the query reaches the audit log.

Link copied to clipboard

Aggregate-bound storage SPI: four primitives that only check natively, translate and execute.

Link copied to clipboard
data class QueryBackendBinding<out B : QueryBackend>(val backend: B, val storage: QueryStorageAdapter)

What a storage supplies for one aggregate's read model: the backend that executes admitted queries and the storage adapter that reports its native facts. The QuerySchemaCatalog compiles those facts with the model's sources and sensitivity; the storage never builds the schema itself.

Link copied to clipboard
fun interface QueryBackendFactory<out B : QueryBackend>

Pairs a query backend of read model B with its storage adapter, for each aggregate.

Link copied to clipboard

The registration SPI of a query storage (design §5.5): under one name, the factories that pair a query backend with its schema provider for each aggregate, one per read model the storage serves.

Link copied to clipboard

Every provider's read models by name; rejects two providers that supply one read model under one name.

Link copied to clipboard
class QueryBudget(val label: String, val maxListSize: Int = 1000, val maxPageSize: Int = 100, val maxPageWindow: Long, val maxFilterNodes: Int = DEFAULT_MAX_FILTER_NODES, val maxFilterValues: Int = 1000, val allowExpensiveOperators: Boolean = true, val maxResidualGroups: Int = DEFAULT_MAX_RESIDUAL_GROUPS)

Size limits and expensive-operator gates for one QueryEntry, checked at admission step 0: against the query as the caller submitted it plus the caller's scope from the edge, before any extension rewrites it. A route's selection (what a load route names in its URL), policy conditions, the model's default scope and cursor tie-breakers therefore never count, and a policy may use a gated operator.

Link copied to clipboard

Where a query came from. The gateway reads it once, when the query is subscribed, together with the caller's scope.

Link copied to clipboard
data class QueryEntryPolicy(val requireExplicitEntry: Boolean = false, val requireAuthenticatedScope: Boolean = false, val http: QueryBudget = QueryBudget.HTTP_DEFAULT, val inProcess: QueryBudget? = null)

What a gateway requires of a query's QueryEntry before admitting it, and the budget each entry runs under.

Link copied to clipboard
class QueryExecutionException(message: String, cause: Throwable? = null) : WowException

A query the caller got right that the server failed to run: a mask strategy that failed, a cursor position that does not encode, a stored record or backend row that breaks integrity (an undeclared event bodyType, a non-finite number), a storage that timed out or failed on some shards. InternalServerError (HTTP 500), never a me.ahoo.wow.query.schema.QueryViolation: the caller cannot fix it by changing the request.

Link copied to clipboard
Link copied to clipboard

Logs every failed query by who must act on it:

Link copied to clipboard
class QueryMetricsObserver(registry: MeterRegistry) : QueryObserver

Publishes one QueryAudit per query as meters (§10 可观测):

Link copied to clipboard
interface QueryObserver
Link copied to clipboard

One gateway operation as QueryAdmission runs it: its queryType, the entry budget of step 0 and the finishing steps 4 to 6.

Link copied to clipboard
fun interface QueryPolicy

A mandatory restriction of an aggregate's queries, evaluated at admission step 2 (QueryAdmission).

Link copied to clipboard
class QueryRequestException(val errorMsg: String, val code: String, name: String = BODY, cause: Throwable? = null, val violation: QueryViolation? = null) : IllegalArgumentException, ErrorInfo

A query request the client got wrong: IllegalArgument with the human errorMsg and one binding error naming where (name, a JSON path or body) and which rule (code, stable and machine-readable). violation is the catalog entry it renders, when the rule is one (QueryViolation.Kind.REQUEST); decoding errors state only a code.

Link copied to clipboard
data class QueryScope(val authenticated: FilterExpression = MatchAllFilter, val declared: FilterExpression = MatchAllFilter)

The caller's scope, split by provenance. Both halves restrict the query; only the authenticated half counts as provided when a gateway requires an authenticated scope.

Link copied to clipboard

Where a caller's scope came from. Only AUTHENTICATED scope is a security boundary.

Link copied to clipboard

An HTTP query whose authenticated scope misses the field that the model requires, rejected because QueryEntryPolicy.requireAuthenticatedScope is on. A declared scope for field does not count.

Link copied to clipboard

One field reference of an AdmittedQuery, resolved once by admission for this request.

Link copied to clipboard

Routes each aggregate to the factory of its route, or to defaultFactory.

Link copied to clipboard
data class SimpleQueryBackendProvider(val name: String, val snapshot: SnapshotQueryBackendFactory? = null, val eventStream: EventStreamQueryBackendFactory? = null) : QueryBackendProvider

Properties

Link copied to clipboard

Whether this comparison ignores case.

Functions

Link copied to clipboard
fun QueryBackend.aggregate(query: AdmittedQuery<AggregationQuery>, budget: QueryBudget? = null): Flux<ObjectNode>

The groups of query. The core plans the residual operators the storage declares RESIDUAL: it removes them from the query it sends down, asks for every group when an operator needs them all, and applies dense fill, HAVING and top-N (or the limit) to the rows that come back. An aggregation without groups always has its summary row: when the backend emits none, because no record matched, the core emits the empty summary (EmptyAggregationValues). When it reads every group, budget's QueryBudget.maxResidualGroups bounds how many it processes, dense fill rows included: HAVING can discard every fill row, so without counting them a sparse fine-grained dense histogram would generate rows until the idle timeout.

Link copied to clipboard
fun <T : Any> Flux<T>.asInProcessQuery(): Flux<T>
fun <T : Any> Mono<T>.asInProcessQuery(): Mono<T>

Runs this query as a nested in-process query; see forInProcessQuery.

Link copied to clipboard

The authenticated part of the caller's scope.

Link copied to clipboard
inline fun checkExecution(value: Boolean, message: () -> String)

Throws a QueryExecutionException with message unless value holds.

Link copied to clipboard
fun QueryBackend.cursor(query: AdmittedQuery<ICursorQuery>): Mono<CursorPage<ObjectNode>>

One cursor page of query: page(Keyset) for one row more than the page, the look-ahead that decides whether a next page exists. The next token encodes the native position of the page's last row, never a value from the rows, so masking cannot leak into it. A token that does not decode for this model and effective sort is rejected as Invalid cursor. before any I/O.

Link copied to clipboard
fun Context.forInProcessQuery(): Context

The context for a query issued from inside a query extension, a policy or a cache loader: the inherited caller scope, route selection and entry are dropped and the entry is QueryEntry.IN_PROCESS, so an HTTP caller's budgets and scope do not leak into a nested lookup.

Link copied to clipboard
fun QueryBackend.list(query: AdmittedQuery<IListQuery>): Flux<ObjectNode>

The records of query, streamed: at most its limit, or all of them when the limit is 0.

Link copied to clipboard
fun QueryModelSchema.maskRecord(record: ObjectNode): ObjectNode

Masks record, a record of this schema's model, by the schema's response masks, in place.

Link copied to clipboard
fun JsonNode.operandValue(pojos: PojoOperands = PojoOperands.NORMALIZE): Any?

This operand as a driver value: null, a String, a Number, a Boolean, or a List of those for an array operand (exact array equality, IN values). A runtime POJO is handled as pojos says.

Link copied to clipboard
fun QueryBackend.paged(query: AdmittedQuery<IPagedQuery>): Mono<PagedList<ObjectNode>>

One page of query with the total: page(Offset(offset, size, withTotal = true)).

Link copied to clipboard
fun ContextView.queryEntry(): QueryEntry
Link copied to clipboard
fun ContextView.queryScope(): FilterExpression

The caller's whole scope, whatever its provenance: what restricts the query.

Link copied to clipboard
fun ContextView.querySelection(): FilterExpression

The route selection the query must stay within; see withQuerySelection.

Link copied to clipboard
fun JsonNode.requiredOperandValue(pojos: PojoOperands = PojoOperands.NORMALIZE): Any

This operand as a non-null scalar driver value, as a range bound or a term needs: a String, Number or Boolean, or a native POJO under PojoOperands.NATIVE.

Link copied to clipboard
fun QueryBackend.single(query: AdmittedQuery<ISingleQuery>): Mono<ObjectNode>

The first record of query: page(Offset(0, 1, withTotal = false)).

Link copied to clipboard
fun Context.withQueryEntry(entry: QueryEntry): Context
Link copied to clipboard
fun Context.withQueryScope(scope: FilterExpression): Context

Appends a declared scope; see withQueryScope.

fun Context.withQueryScope(scope: QueryScope): Context

Appends scope to the caller's scope; its authenticated half also to authenticatedQueryScope.

Link copied to clipboard
fun Context.withQuerySelection(selection: FilterExpression): Context

Appends a route selection: what a load route names in its URL, the aggregate id, a version range, and the tenant and owner its path carries. It is an operation constraint, not caller scope: admission appends it at step 2 with the caller scope, after every QueryFilter, so a rewrite can neither see nor remove it, and it is neither budgeted as scope nor reported among the audit's scope fields.