Expose document queries
Each type of query document needs to be served by its own dedicated endpoint. Thanks to our GraphQL engine we don’t have to write them by hand, but we can instead use one generic endpoint that gets multiplied into one query per row type we declare.
We use this general qlive-graphql pattern to serve out query documents.
Here we see the setup for logic bean containing such a generic QueryDocumentService endpoint.
import io.github.qlivedev.qlive.api.annotation.QLiveLogic;import io.github.qlivedev.qlive.api.annotation.QLiveQuery;import io.github.qlivedev.qlive.api.annotation.QLiveTypeParam;import io.github.qlivedev.qlive.model.QueryConfig;import io.github.qlivedev.qlive.model.QueryDocument;import io.github.qlivedev.qlive.runtime.query.QueryDocumentService;import io.github.qlivedev.qlivetest.domain.tables.pojos.Bar;import io.github.qlivedev.qlivetest.domain.tables.pojos.Foo;import graphql.schema.DataFetchingEnvironment;import jakarta.validation.constraints.NotNull;import org.springframework.context.annotation.Lazy;
/// Example logic@QLiveLogicpublic class QueryLogic{
private final QueryDocumentService queryDocumentService;
public QueryLogic( @Lazy QueryDocumentService queryDocumentService ) { this.queryDocumentService = queryDocumentService; }
/// Queries [T] objects based on the given query config @QLiveQuery public <T> @NotNull QueryDocument<T> queryDocument( @QLiveTypeParam( namePattern = "query*Document", typeNamePattern = "*Document", types = { Foo.class, Bar.class } ) Class<T> type, DataFetchingEnvironment env, @NotNull QueryConfig config ) { return queryDocumentService.buildQuery(type, env, config) .selectByFilter(true) .execute(); }}@QLiveTypeParam controls how the generic method is turned into many concrete methods:
typeslists all rowTypes we want.namePatterndeclares the name pattern for the GraphQL method.typeNamePatterndeclares the pattern for the result type name
So here we declare Foo.class and Bar.class as the row types we want. The result schema corresponding to this will
be
type QueryType { "Queries Foo objects based on the given query config" queryFooDocument(config: QueryConfig!): FooDocument! "Queries Bar objects based on the given query config" queryBarDocument(config: QueryConfig!): BarDocument! # ...}selectByFilter
Section titled “selectByFilter”The one option worth understanding. It decides how far a QueryConfig
posted by a browser may reach.
false(the default) is the strict mode. A filter or sort path may only name a field the query actually selects. That is what makes the query document itself the security boundary.truelets a path extend the plan: relations it crosses get joined, and the field it ends at gets selected. More convenient, and a wider surface – a client can then read through relations the query did not select.
qlive-test passes true because it is a test application that only has security examples and no real security.
- Read about why the document can be a security boundary
- A page size cap, a default sort or a standing condition per type: Define the domain.
- Row-level security, or redefining what “no config” means: Customize a document query.