As you can see in Usage guide, we need to provide floor-plan/compile-floor-plan a map describing the floor-plan. You've got some idea about how such a floor-plan looks like as you glanced the example in Overview section. Now let's walk through each key of the floor-plan map in details.

Notes:

  • SQL snippets have been simplified for explanation purpose
  • Backticks are used as quote marks
  • For clarity, part of the floor-plan may not be included

1 :idents

Idents are the root of all queries. From an SQL dbms perspective, you must start your graph query from somewhere, and it's a table.

1.1 Keyword idents

Keyword idents can be defined as simple as:

Floor-plan
Query
SQL output
{:idents {:people/all "person"}}
`[{:people/all [:person/id :person/name]}]
SELECT `id`, `name` FROM `person`

1.2 Vector idents

These are idents whose key implies some condition. Instead of providing just the table, you provide the column (as a namespaced keyword) whose value match the ident argument found in the ident key.

For example, the following vector ident will require a floor-plan like:

Vector ident
Floor-plan
Query
SQL output
;; dispatch-key: `:person/by-id`
;; ident arguments: `1`
[:person/by-id 1]
{:idents {:person/by-id :person/id}}
[{[:person/by-id 1] [:person/id :person/name]}]
SELECT `id`, `name` FROM `person` WHERE `person`.`id` = 1

2 :joins

Each entry in :joins describes the "path" from the source table (of the source entity) to the target table (and optionally the join table).

Let's see some examples.

Example 1: Join column living in source table

Data
Query
Floor-plan
SQL output
Result

Assume table cow contains:

id color
10 white
20 brown

and table farmer has:

id name cow_id
1 jon 10
2 mary 20

and you want to get a farmer along with their cow using the query:

[{[:farmer/by-id 1] [:farmer/name {:farmer/cow [:cow/id :cow/color]}]}]

For the join :farmer/cow, table farmer is the source and table cow is the target.

then you must define the join "path" like this:

{:joins {:farmer/cow [:farmer/cow-id :cow/id]}}

the above join path says: start with the value of column farmer.cow_id (the join column) then find the correspondent in the column cow.id.

Internally, Walkable will generate this query to fetch the entity whose ident is [:farmer/by-id 1]:

SELECT `farmer`.`name`, `farmer`.`cow_id` FROM `farmer` WHERE `farmer`.`id` = 1

the value of column farmer.cow_id will be collected (for this example it's 10). Walkable will then build the query for the join :farmer/cow:

SELECT `cow`.`id`, `cow`.`color` FROM `cow` WHERE `cow`.`id` = 10
{[:farmer/by-id 1] #:farmer{:number 1,
                            :name "jon",
                            :cow #:cow{:index 10,
                                       :color "white"}}}

Example 2: A join involving a join table

Data
Query
Floor-plan
SQL output
Result

Assume the following tables:

source table person:

id name
1 jon
2 mary

target table pet:

id name
10 kitty
11 maggie
20 ginger

join table person_pet:

person_id pet_id adoption_year
1 10 2010
1 11 2011
2 20 2010

you may query for a person and their pets along with their adoption year

[{[:person/by-id 1] [:person/name {:person/pets [:pet/name :person-pet/adoption-year]}]}]

then the :joins part of our floor-plan is as simple as:

{:joins {:person/pets [:person/id :person-pet/person-id
                       :person-pet/pet-id :pet/id]}}

Walkable will issue an SQL query for [:person/by-id 1]:

SELECT `person`.`name` FROM `person` WHERE `person`.`id` = 1

and another query for the join :person/pets:

SELECT `pet`.`name`, `person_pet`.`adoption_year`
FROM `person_pet` JOIN `pet` ON `person_pet`.`pet_id` = `pet`.`id` WHERE `person_pet`.`person_id` = 1

and our not-so-atonishing result:

{[:person/by-id 1] #:person{:id 1,
                            :name "jon",
                            :pets [{:pet/id 10,
                                    :pet/name "kitty"
                                    :person-pet/adoption-year 2010}
                                   {:pet/id 11,
                                    :pet/name "maggie"
                                    :person-pet/adoption-year 2011}]}}

Example 3: Join column living in target table

No big deal. This is no more difficult than example 1.

Data
Floor-plan

Assume table farmer contains:

id name
1 jon
2 mary

and table cow has:

id name owner_id
10 white 1
20 brown 2

The floor-plan for this example can be a good exercise for the reader of this documentation. (Sorry, actually I'm just too lazy to type it here :D )

3 :reversed-joins

A handy way to avoid typing a join whose path is just reversed version of another.

Floor-plan
Queries

The floor-plan for such a join is straightforward:

;; floor-plan
{:joins          {:farmer/cow [:farmer/cow-id :cow/id]}
 :reversed-joins {:cow/owner :farmer/cow}}

so you can go both ways:

  • find the cow of a given farmer
[{[:farmer/by-id 1] [:farmer/name {:farmer/cow [:cow/id :cow/color]}]}]
  • find the owner of a given cow
[{[:cow/by-id 10] [:cow/id :cow/color {:cow/owner [:farmer/name]}]}]

Also, another reason to use :reversed-joins is that it helps with semantics.

4 :columns

A set of available columns must be provided at compile time so Walkable can pre-compute part of SQL query strings.

;; floor-plan
{:columns #{:farmer/name :cow/color}}

Walkable will automatically include columns found in :joins paths so you don't have to.

Please note: keywords not found in the column set will be ignored. That means if you forget to include any of them, you can't use the missing one in a query's property or filter.

The rationale for having a pre-defined set of columns is that your query resolver doesn't have to limit itself to an SQL database as a single source of data. If Walkable can't match a keyword to a column, an ident or a join, it will be passed down to the next plugin in the Pathom plugin chain.

On the other hand, you don't have to include every single column in your database if you know you will never use some of them in a query's property or filter.

5 :cardinality

Idents and joins can have cardinality of either :many (which is default) or :one. You declare that by their dispatch keys:

;; floor-plan
{:cardinality {:person/by-id :one
               ;; you can skip all `:many`!
               :people/all   :many}}

6 :extra-conditions

Please see documentation for Filters

7 :emitter

Please see documentation for Emitters

8 :pseudo-columns

Please see documentation for Pseudo-columns

9 :pagination-fallbacks

Please see documentation for Pagination

10 :aggregators

Please see documentation for Aggregators

results matching ""

    No results matching ""