Including :keys
in an advanced query changes the structure of the query results.
When I say the structure changes, I mean is that the query result content remains the same, but the way Logseq organizes the data changes. This change isn’t visible to the user.
Let’s explore the specific changes that occur.
Essentially, Logseq changes the data types used to contain the query results from one type to another. Specifically, the query result structure changes in this way:
- from a list of the data specified by the
:find
clause expressions.
- into a list of maps. In the list, each map represents a single evaluation of the
:find
clause. Within each map there are a number of keys containing the result values, with as many keys as there are symbols specified after :keys
.
In the :keys
result, each map gets its key name from the values specified after :keys
. These values are referred to as symbols. For example, the symbols from OP’s query are status and task.
Let’s peek at the result of the original advanced query to compare the result with and without :keys
. This can be done by changing the :view
function to :view :pprint
Advanced Query
Advanced query used to see the results. Notice the modified :view function.
{:query
[:find ?status (pull ?todo [*])
:keys status task ;; comment this out
:where
[?todo :block/marker ?status]
[(contains? #{"NOW" "LATER"
"DOING" "TODO"
"IN-PROGRESS" "WAIT"
"WAITING"}
?status)]]
:result-transform
;; same as above
:view :pprint
}
query results
Comparing the advanced query result both with and without the :keys
feature.
Without :keys
. This is the “default” query result structure. It doesn’t use :keys status task
.
(
"TODO"
{:block/uuid #uuid "6682d233-653e-47b4-9196-9fd5b19e60c8",
:block/content "TODO do laundry",
:db/id 8926,
:block/marker "TODO"}
"TODO"
{:block/uuid #uuid "6682d234-ad2f-4584-b660-60c22d1074bd",
:block/content "TODO canned tomato paste",
:db/id 9542,
:block/marker "TODO"}
)
With :keys
shows how the structure of the query result changes when including :keys status task
. The content is the same but the data structure is different. Try to notice what changed and what stayed the same.
(
{:status "TODO",
:task {:block/uuid #uuid "6682d233-653e-47b4-9196-9fd5b19e60c8",
:block/content "TODO do laundry",
:db/id 8926,
:block/marker "TODO"}}
{:status "TODO",
:task {:block/uuid #uuid "6682d234-ad2f-4584-b660-60c22d1074bd",
:block/content "TODO canned tomato paste",
:db/id 9542,
:block/marker "TODO"}}
)
Interpretation
By using :keys
in an advanced query, instead of a getting… ( "a list of strings" {:and "maps"} )
, logseq returns a ( {:list "of nested"} {:maps "and strings"} )
.
Aside: About data structures
In Clojure, the syntax indicates the data type:
"This is a string"
"This is a
multi-line string"
0 <- number
1.7 <- number
:i-am-a-keyword <- keyword
symbol <- used for variables, evaluates to something else
(1 2 3 4 5) <- parentheses make lists
{:keyword "string"} <- curly braces make maps storing key-value pairs.
{"string" {:another "map"}} <- Maps can contain other data types
Anyway…
In the result with :keys
, each component of the :find
clause corresponds to the symbol from :keys
in the same position. Each :key
symbol becomes the map keyword for that :find
clause return value.
Annotated results
Without :keys
. Query results without using :keys
with data type annotations:
( ;; list data type
"TODO" ;; string data type ;; :find ?status
{ ;; map data type ;; :find (pull ?todo [*])
:block/uuid #uuid "6682d233-653e-47b4-9196-9fd5b19e60c8",
:block/content "TODO do laundry",
:db/id 8926,
:block/marker "TODO"}
"TODO"
{:block/uuid #uuid "6682d234-ad2f-4584-b660-60c22d1074bd",
:block/content "TODO canned tomato paste",
:db/id 9542,
:block/marker "TODO"}
)
With :keys status task
. Query results when using :keys status task
:
( ;; list data type
{ ;; map data type
:status "TODO", ;; :find ?status :keys status
:task { ;; :find (pull ?todo [*]) :keys task
:block/uuid #uuid "6682d233-653e-47b4-9196-9fd5b19e60c8",
:block/content "TODO do laundry",
:db/id 8926,
:block/marker "TODO"
}
}
{:status "TODO",
:task {:block/uuid #uuid "6682d234-ad2f-4584-b660-60c22d1074bd",
:block/content "TODO canned tomato paste",
:db/id 9542,
:block/marker "TODO"}
}
)
Details
return map
The name for the :keys
syntax is a return map in the Datomic query language. From the Datomic documentation:
Return Maps
Supplying a return-map will cause the query to return maps instead of tuples. Each entry in the :keys / :strs / :syms clause will become a key mapped to the corresponding item in the :find clause.
Return maps also preserve the order of the :find
clause.
Datomic grammar
The Datomic EBNF grammar specification shows the relationship between return maps and return-keys. Here’s a simplified version showing the relationship.
Datomic Query Argument Grammar
(simplified)
EBNF rule |
definition |
query |
[find-spec return-map-spec? with-clause? inputs? where-clauses?] |
find-spec |
‘:find’ (variable OR pull-expr OR aggregate) |
return-map-spec |
(return-keys OR return-syms OR return-strs) |
return-keys |
‘:keys’ plain-symbol+ |
return-syms |
‘:syms’ plain-symbol+ |
return-strs |
‘:strs’ plain-symbol+ |
plain-symbol |
symbol that does not begin with “$”, “?”, or “%” |
with-clause |
‘:with’ variable+ |
Syntax used in grammar
syntax symbol |
meaning |
‘’ |
literal |
() |
grouping |
+ |
one or more |