datalog

David Bieber 2020-12-18T04:25:19.058800Z

Hello! I'm learning datalog and thought I would share what I'm struggling with in case you can help me along. I'm working on a query now. The goal is to find all "blocks" with >100 descendent blocks. Here's what I have so far (not working yet).

let ancestorrule=`[ 
   [ (ancestor ?child ?parent) 
        [?parent :block/children ?child] ] 
   [ (ancestor ?child ?a) 
        [?parent :block/children ?child ] 
        (ancestor ?parent ?a) ] ] ]`;
window.roamAlphaAPI.q(`[ 
  :find 
      ?parent
  :in $ ?count % 
  :where 
    [?parent :block/string]
    [?block :block/string]
    (ancestor ?block ?parent)
	[(count ?block) ?block_count]
	[> ?block_count ?count]
  ]`, 100, ancestorrule);
I'm going to keep poking at it and trying to debug, but lmk if you know how to fix it and get it working. The current error is No protocol method ICounted.-count defined for type number: 4. My understanding is that the way I've written it, I'm trying to count each block (which doesn't make sense) -- whereas what I really want is to count the total number of blocks satisfying the other criteria. Probably I need to figure out subqueries or something like that. (And clearly this isn't an active channel -- so feel free to ignore or redirect me :))

quoll 2020-12-18T16:48:42.071600Z

Responding to the original question… Your error message gives you a hint as to where to look. It says that it’s looking for the `ICounted` protocol (specifically, the `-count` function on that protocol), on the number 4. That function is typically called when you call `count` on something, and looking at your query I see that you do. You’re trying to call `count` on the `?block` var. However, that’s a var that gets bound via a rule, and represents a binding for a resource. This is not a “countable” thing. If you’re trying to find out how many things get bound to `?block` then that would need an aggregate function, which `count` is not doing here. Also, it looks like the var `?block` is getting bound to the number 4. That’s totally unexpected, because it’s an entity. I think Datascript might allow arbitrary things in the entity position, but that’s weird if the data has done that.

quoll 2020-12-18T16:51:06.071800Z

Your second post is much better. Now you’re getting the aggregate that you needed, via the :find clause.

quoll 2020-12-18T16:52:42.072Z

I’ve fallen behind on the implementations a bit, but to my knowledge: • aggregates are only possible in the :find clause, which is the final step before a query returns. • There is no syntax for subqueries.

quoll 2020-12-18T16:57:28.072200Z

To my knowledge, the best you can do is have the results of one query passed in as an argument for the next query… which effectively makes that next query a subquery for you. You’re doing that, but with a filter in the middle. You could avoid the filter and just do the comparison in the query itself:

let ancestorrule=`[ 
   [ (ancestor ?child ?parent) 
        [?parent :block/children ?child] ]
   [ (ancestor ?child ?a) 
        [?parent :block/children ?child ] 
        (ancestor ?parent ?a) ] ] ]`;
.filter((data, index) => {return data[1] > 100});
window.roamAlphaAPI.q(`
[:find ?text ?block ?childcount
:in $ ?limit [[?block ?childcount]]
:where
[?block :block/string ?text]
[(< ?childcount ?limit)]
]
`, 100, window.roamAlphaAPI.q(`
         [:find ?ancestor (count ?block)
          :in $ ?count % 
          :where 
            [?ancestor :block/string]
            [?block :block/string]
            (ancestor ?block ?ancestor)]
        `, 100, ancestorrule));

quoll 2020-12-18T16:57:46.072400Z

But that’s the closest I know how to do 🙂

David Bieber 2020-12-18T04:48:18.059800Z

I've gotten it working using some non-datalog in the middle of the query :man-shrugging:

let ancestorrule=`[ 
   [ (ancestor ?child ?parent) 
        [?parent :block/children ?child] ]
   [ (ancestor ?child ?a) 
        [?parent :block/children ?child ] 
        (ancestor ?parent ?a) ] ] ]`;
let results = window.roamAlphaAPI.q(`
[:find ?ancestor (count ?block)
  :in $ ?count % 
  :where 
    [?ancestor :block/string]
    [?block :block/string]
    (ancestor ?block ?ancestor)]
`, 100, ancestorrule).filter((data, index) => {return data[1] > 100});
window.roamAlphaAPI.q(`
[:find ?text ?block ?childcount
:in $ [[?block ?childcount]]
:where
[?block :block/string ?text]
]
`, results);
I wonder if I could do it all in datalog though.