Dynamic Query
A Dynamic Query is a here whose body is a declaration rather than code: its programming_lang is Query. It reads the records of one Dynamic Entity, adds the records joined to them through reference: fields, and returns them as a named list. It is for the common case of an Endpoint that only reads Dynamic Entity data, which would otherwise need a Scala method body.
Because nothing is compiled or run, a Dynamic Query is available even where user-supplied code is switched off, and a reviewer approving one (see here) reads a declaration, not a program. A Dynamic Query only reads, so its request_verb must be GET.
The body
{
"from": "activity",
"select": ["activity_id", "name", "city"],
"where": { "city": "eq:Berlin" },
"join": [
{ "entity": "operator", "on": "operator_id",
"fields": { "operator_legal_name": "legal_name" } },
{ "entity": "certificate", "on": "activity_id",
"cardinality": "at_most_one", "pick": "latest_by:issue_date",
"fields": { "certificate_number": "number" } },
{ "entity": "inspection", "on": "activity_id",
"cardinality": "exists", "as": "inspected" }
],
"envelope": { "rows": "activities", "count": "count" }
}
from(required): the Dynamic Entity whose records are returned.select: the fields of those records to return, in this order. All of them when absent.where: filters on them, written as the list Endpoint'sobp_filtervalues ("field": "eq:value", or a list of such strings for several filters on one field). Filtered fields must be declared"indexed": true.join: the related records to add to each record. See below.envelope:rowsnames the list (by default the entity's own list name) andcount, when given, names a field holding how many records match in all, not only on this page.
Keys that are not listed here are rejected, so a misspelt key is reported rather than ignored.
Joins
A reference: field links two entities, and a join can read it from either end. A forward join follows the record's own field to the record it names: an activity's operator_id names one operator. A reverse join finds the records of the other entity whose field names this record: certificates whose activity_id names the activity. The join only names the other entity and the field it is linked on; OBP sees which entity holds that field and works out the direction. Only for a self-reference, such as employee.manager_id of type reference:employee (the manager, or the direct reports?), must the join add "direction": "forward" or "reverse".
cardinality says what to do with the matching records:
at_most_onecopies the fields of one record into the result (fieldsmaps each result name to a field of that record). A forward join isat_most_oneunless it says otherwise. A reverse join that isat_most_onemust say how to choose when several records match, withpick:latest_by:<field>orearliest_by:<field>. A record without that field is never chosen ahead of one with it, and ties are broken by record id.manygives a list namedas, one object per matching record with thefieldsgiven, ordered byorder(latest_by:orearliest_by:), or by record id when absent. Only a reverse join can bemany.existsgives one value namedas:true_valueif any record matches,false_valueif none (JSON true and false by default).
A join's where filters the other records before the cardinality applies, with the same operators as above. The field a reverse join is linked on must be declared "indexed": true.
Calling a Dynamic Query
A caller can narrow the result with the list Endpoint's own parameters: obp_filter, obp_sort_by, obp_sort_direction, obp_limit, obp_offset, obp_exists and obp_not_exists. They are added to the declaration's where, never replace it.
A Dynamic Query can be public: callable without logging in. That needs all three of: no Roles on the Dynamic Resource Doc, an error list that does not name OBP-20001 (which is how a Dynamic Resource Doc says it requires login), and public access on every Dynamic Entity it reads. Whether data can be public is decided on the entity; a query cannot make an entity's records public.
What a caller can see
A Dynamic Query never shows a caller more than they could read directly. The primary control is the Dynamic Entities' own access: the caller must be able to read every Dynamic Entity the query reads (its read Role, public access, or row-level access), exactly as through that entity's own Endpoints. Roles on the Dynamic Resource Doc itself are optional; they can only narrow who may call the query, never widen what a caller can read. Without that access the answer is OBP-40066 (403), naming every such entity with the Role that would let the caller read it and the bank it is needed at. Only shared records are used, never a User's personal records. For an entity with row-level access only the records the caller's access list allows are used. A field that requires a read Role is null (or left out, when not selected) unless the caller holds that Role, and the caller cannot filter or sort on it. A joined value is null when there is no matching record, when the caller may not read it, or when it lacks the field: these cases look the same, so a join never reveals that a hidden record exists.
Checking a body
Creating, updating or validating a Dynamic Query checks it against the Dynamic Entity definitions of its space, and POST /obp/v7.0.0/management/dynamic-resource-docs/compile does the same with programming_lang Query. Problems are reported as OBP-40065.
POST /obp/v7.0.0/management/dynamic-resource-docs/explain shows how a Dynamic Query would be answered, without reading any record, so its author can check that the SQL is sane and that access is what they expect: each read it would make, in order, with the SQL OBP builds for it (every value shown as ?) or a description when it goes through the record provider; every Dynamic Entity it reads, with its read Role and whether the caller may read it; every read-restricted field it touches; and the refusal a caller would get. It can explain the query for the requesting User or for a caller who is not logged in, and with the parameters a caller would add. The API Manager's Explain button uses it.