Getting Started on Pathom core engine
Query Notation Introduction
A query
is a vector that lists the items you want. A keyword requests a scalar (opaque) value, and
a map indicates a to-many or to-one join (resolved at runtime using database content).
Queries are always "relative" to some starting context (which is typically supplied via parameters or by a top-level join).
If you want to obtain the name and age of "some" person:
[:person/name :person/age]
If you want to obtain a person’s name and the street of their address you might write this:
[:person/name {:person/address [:address/street]}]
where we imagine that the underlying database has some kind of normalization that needs to be traversed in order to satisfy the address data.
The result of running a query is a map containing the result (in the same recursive shape as the query):
Running [:person/name :person/age]
against the person "Sam" might give:
{:person/name "Sam" :person/age 32}
Running [:person/name {:person/address [:address/street]}]
against that same person might give:
{:person/name "Sam" :person/address {:address/street "111 Main St."}}
The query establishes the request and expectation. Interpreting and satisfying these queries from some arbitrary data source is the job of a query parser/interpreter. This library gives you tools for quickly building the latter.
Parsing Context
The elements of a graph query are relative, which means, they have a contextual meaning. If you ask for a person’s name, the implication is that you are querying a "person entity"; however, the other required bit of information is which person. Thus, elements of a query cannot be fulfilled unless they are rooted in a context. This applies to joins as well (e.g. what is the current person’s address?); however, once you’ve resolved the context of the root of some graph query the joins simply describe navigation from that context (the person) to another (their address) via a relation that is either already described in the underlying data source itself or within the code you provided, that can figure it out.
As the parser moves through a query like [:person/name {:person/address [:address/street]}]
it first starts
with some context (e.g. "Sam"). When it finds a join it processes the subquery against a new context (e.g. Sam’s address)
to give the result:
{:person/name "Sam" :person/address {:address/street "111 Main St."}}
So, there is always a context at any given point when parsing a query. This context is either established at startup by resolving a specific entity, or is the entity (or entities if to-many) that have been reached by processing the joins of the query.
Parsing Environment and The Reader
The parsing environment is simply a map that carries along with it some data while parsing (and can be augmented as you go). It establishes the meaning of the "current context", containing anything you wish (via namespaced keywords) and can be seen in any code that you plug-in to process the query.
There are some pre-defined (namespaced) keys which have special meaning to the parser. In particular
:com.wsscode.pathom.core/reader
can be used to supply reader(s) for the parser to use. The reader
can be a map from
attributes to functions, a plain function, or even a vector of functions. It is asked to read the value for the elements
of the query using the current environment. We’ll expand on that as we go or you can read more in the Readers section.
Updating the environment in mid-query
During the process of joins it’s possible to modify the environment map that will be used
when processing the join. To do that, you must return the key ::p/env
with the new full
environment. As in this example:
(ns com.wsscode.pathom.book.core.join-env-update
(:require [com.wsscode.pathom.core :as p]
[com.wsscode.pathom.connect :as pc]))
(pc/defresolver env-modifier [env input]
{::pc/output [{:change-env [:val]}]}
{:change-env {:val 123
::p/env (assoc env ::demo-env-key "modified")}})
(pc/defresolver env-read-thing [{::keys [demo-env-key]} input]
{::pc/cache? false
::pc/output [:env-data]}
{:env-data demo-env-key})
(pc/defresolver global-thing [{::keys [demo-env-key]} input]
{::pc/cache? false
::pc/output [:env-data]}
{:env-data demo-env-key})
(def register
[env-modifier env-read-thing])
(def parser
(p/parser
{::p/env {::p/reader [p/map-reader
pc/reader2
pc/open-ident-reader
p/env-placeholder-reader]
::demo-env-key "original"
::p/placeholder-prefixes #{">"}}
::p/plugins [(pc/connect-plugin {::pc/register register})
p/error-handler-plugin
p/trace-plugin]}))