*Edited:* How to use predefined rules/predicates in custom rules and how to use recursive rules?

Hi,
I am using the simple Habit Tracker based on advanced queries from this post. Based on the 3rd example about chains of movie sequels in the last chapter of learndatalogtoday.org, I thought to also generate a chain of consecutive days during which a habit was done (a streak). This is what my current implementation looks like:

#+BEGIN_QUERY
{
 :title [:b "Habit streak"]
 :query
 [
  :find (count ?datename) ?datename
  :in $ ?today %
  :where
  [?p :page/journal? true]  ;; can be omitted?
  [?b :block/page ?p]
  [?p :block/original-name ?datename]
  [?p_today :page/journal? true]  ;; can be omitted?
  [?b_today :block/page ?p_today]
  [?p_today :block/journal-day ?today]
  (connected-block ?b ?b_today)
 ]
 :inputs [:today [
  [(next-day ?d1 ?d2) [(+ 1 ?d1) ?d1_next] [(== ?d1_next ?d2)]]
  [(pages-next-day ?p1 ?p2) [?p1 :block/journal? true] [?p2 :block/journal? true] [?p1 :block/journal-date ?d1] [?p2 :block/journal-date ?d2] (next-day ?d1 ?d2)]  ;; can check for journal page be omitted?
  [(blocks-next-day ?b1 ?b2) [?b1 :block/page ?p1] [?b2 :block/page ?p2] (pages-next-day ?p1 ?p2)]
  [(tracked-block ?b) (property ?b :type "tracker") (property ?b :test true)]
  [(connected-block ?b1 ?b2) (tracked-block ?b1) (tracked-block ?b2) (blocks-next-day ?b1 ?b2)]
  [(connected-block ?b1 ?b2) (connected-block ?b1 ?bx) (connected-block ?bx ?b2)]
 ]]
}
#+END_QUERY

Unfortunately, I cannot test the query’s logic as I get the following error:

:exception #error {:message "Unknown rule 'property in (property ?b :type \"tracker\")", :data {:error :query/where, :form (property ?b :type "tracker")}}, :line 698}

The rule property works when using it in the :where clause. It is a predefined rule or predicate. So my question now is: how can I use predefined rules, i.e., property, within rules that I define myself? Using .property instead of property does not help.

Best,
Niklas

I was unaware that the rule/predicate get is available within custom rules. So now my looks like that:

#+BEGIN_QUERY
{
 :title [:b "Habit total"]

 :query
 [
  :find (count ?datename) ?datename
  :in $ ?today %
  :where
  [?p :page/journal? true]  ;; can be omitted?
  [?b :block/page ?p]
  [?p :block/original-name ?datename]

  [?p_today :page/journal? true]  ;; can be omitted?
  [?b_today :block/page ?p_today]
  [?p_today :block/journal-day ?today]

  (connected-block ?b ?b_today)
 ]
 :inputs [:today]
 :rules [
  [(next-day ?d1 ?d2) [(+ 1 ?d1) ?d1_next] [(== ?d1_next ?d2)]]
  [(pages-next-day ?p1 ?p2) [?p1 :block/journal? true] [?p2 :block/journal? true] [?p1 :block/journal-date ?d1] [?p2 :block/journal-date ?d2] (next-day ?d1 ?d2)]  ;; can check for journal page be omitted?
  [(blocks-next-day ?b1 ?b2) [?b1 :block/page ?p1] [?b2 :block/page ?p2] (pages-next-day ?p1 ?p2)]

;; 
;; the following two lines have changed
;; 
  [(test-property ?b ?type ?testval) [?b :block/properties ?props] [(get ?props ?type) ?val] [(== ?val ?testval)]]
  [(tracked-block ?b) (test-property ?b :type "tracker") (test-property ?b :test true)]

  [(connected-block ?b1 ?b2) (tracked-block ?b1) (tracked-block ?b2) (blocks-next-day ?b1 ?b2)]
  [(connected-block ?b1 ?b2) (connected-block ?b1 ?bx) (connected-block ?bx ?b2)]
 ]
}
#+END_QUERY

When I execute this advanced query in a rather empty graph, logseq seems to freeze. At least, I do not get any error in the console and the block looks like this:


But it doesn’t change over several minutes.

Is the above query consuming to much compute ressources, i.e., inefficient? Or are advanced queries not made for such a task?

EDIT1

According to the task manager, CPU and memory of the logseq process increase after starting this query. CPU goes up to 25-30% and memory slowly increases (100 MB over 5 minutes, so really not much).

EDIT2

I tried to reduce the logical overhead in the above queries. Now, I only want to find the number of consecutive journal pages from today’s journal page. Assume, today is 2023-08-05 and you have additional journal pages for 2023-08-04, 2023-08-03, 2023-08-01, but not for 2023-08-**02**. I want the query to give out the number 2 as there are two journal pages directly connected to today’s journal page. This is the query I wrote:

#+BEGIN_QUERY
{
 :title [:b "Consecutive journal days"]

 :query
 [
  :find (count ?p)
  :in $ ?today %
  :where
  [?p :block/journal? true]  ;; we are interested in journal pages

  ;; todays journal page
  [?p_today :block/journal? true]
  [?p_today :block/journal-day ?today]
  
  ;; ?p should be connected by consecutive daily journal pages to todays journal page
  (pages-next-day ?p ?p_today)
 ]
 :inputs [:today]
 :rules [
  ;; test whether the day ?d1 is directly followed by ?d2
  [(next-day ?d1 ?d2) [(+ 1 ?d1) ?d1_next] [(== ?d1_next ?d2)]]

  ;; test whether ?p1 and ?p2 are journal pages and test whether they are directly neighbored
  [(pages-next-day ?p1 ?p2) [?p1 :block/journal? true] [?p2 :block/journal? true] [?p1 :block/journal-day ?d1] [?p2 :block/journal-day ?d2] (next-day ?d1 ?d2)]
  ;; find whether two journal pages are connected by an intermediate journal page ?px
  ;; uncommenting this line will freeze logseq or run some infinite cycle...
  ;; [(pages-next-day ?p1 ?p2) (pages-next-day ?p1 ?px) (pages-next-day ?px ?p2)]
 ]
}
#+END_QUERY

The above query is only asking for the number of yesterday’s journal pages, which is obviously either 1 or 0. To allow the query to run behind yesterday’s journal page I added this recursive statement [(pages-next-day ?p1 ?p2) (pages-next-day ?p1 ?px) (pages-next-day ?px ?p2)] in the very end of the :rules. When I uncomment this rule and run the query, logseq freezes or goes into an infinite cycle.

Based on this observation, I wondered whether logseq or datascript are able to run recursive rules? According to GitHub - tonsky/datascript: Immutable database and Datalog query engine for Clojure, ClojureScript and JS recursive rules are supported. I took a closer look on the mentioned rules there and in mentioned chapter 8 of the learndatalogtoday.org tutorial. It seems that you can “call” the same rule within a new rule definition only once, but I do it twice in the above query. Therefore, I changed the second rule definition into:

[(pages-next-day ?p1 ?p2) [?p1 :block/journal? true] [?px :block/journal? true] [?p1 :block/journal-day ?d1] [?px :block/journal-day ?dx] (next-day ?d1 ?dx) (pages-next-day ?px ?p2)]

and then it works! It gives the correct output and does not run infinitely.

EDIT 3

I came across two new problems.

  1. The variable in :block/journal-day is not a date variable, it is a basic integer. So adding +1 with [(+ 1 ?d1) ?d1_next] works for within month dates, but is not capable of working across months. For example, 2023-07-31 is 20230731 and naively this would give 2023-07-32 as the next date. Similarly, the date before 2023-08-01 is 2023-08-00 which also does not exist. Does anyone have an idea how to workaround this limitation? I guess the easiest way would be to write a Clojure function that sees problematic dates and adjusts automatically…
  2. I implemented my original idea now in this query with the correct recursive strategy:
#+BEGIN_QUERY
{
 :title [:b "Habit streak"]

 :query
 [
  :find (count ?b) ?b
  :in $ ?today %
  :where
  [?b_today :block/page ?p_today]
  [?p_today :block/journal-day ?today]

  (connected-block ?b ?b_today)
 ]
 :inputs [:today]
 :rules [
  [(next-day ?d1 ?d2) [(+ 1 ?d1) ?d1_next] [(== ?d1_next ?d2)]]
  [(pages-next-day ?p1 ?p2)
   [?p1 :block/journal-day ?d1]
   [?p2 :block/journal-day ?d2]
   (next-day ?d1 ?d2)] 
  [(blocks-next-day ?b1 ?b2)
   [?b1 :block/page ?p1]
   [?b2 :block/page ?p2]
   (pages-next-day ?p1 ?p2)]

  [(test-property ?b ?type ?testval)
   [?b :block/properties ?props]
   [(get ?props ?type) ?val]
   [(== ?val ?testval)]]
  [(tracked-block ?b) (test-property ?b :type "tracker") (test-property ?b :test true)]

  [(connected-block ?b1 ?b2)
   (tracked-block ?b1)
   (tracked-block ?b2)
   (blocks-next-day ?b1 ?b2)]
  [(connected-block ?b1 ?b2)
   (tracked-block ?b1)
   (tracked-block ?bx)
   (blocks-next-day ?b1 ?bx)
   (connected-block ?bx ?b2)]
 ]
}
#+END_QUERY

It works correctly for tracking between today and yesterday. But it is unable to retrieve information from two or more days ago. I cannot spot the error that I am making. Can someone see the error?

As a workaround for the above mentioned calendar / date issue, I decided to add backlinks from each journal page to the day before, such that I get a chain of journal pages. This is not the most beautiful solution. But the backlink can be automatized by using the <% yesterday %> dynamic variable in a daily journal template. The full query looks like that:

{
 :title [:b "Habit streak"]

 :query
 [
  :find (count ?p)
  :in $ ?today ?habit %
  :where
  [?p :block/journal? true]  ;; we are interested in journal pages

  ;; todays journal page
  [?p_today :block/journal? true]
  [?p_today :block/journal-day ?today]

  ;; ?p should be connected by consecutive daily journal pages to todays journal page
  (pages-next-day ?p ?p_today ?habit)
 ]
 :inputs [:today :test]
 :rules [
 ;; similar to predefined property rule
 [(test-property ?b ?type ?testval)
   [?b :block/properties ?props]
   [(get ?props ?type) ?val]
   [(== ?val ?testval)]]
  ;; check whether specific habit was done in tracker block
  [(habit-block ?b ?habit)
   [test-property ?b :type "tracker"]
   [test-property ?b ?habit true]]
  
  [(pages-next-day ?p1 ?p2 ?habit)
   [?b2 :block/page ?p2]
   ;; only test day that is linking backwards for done habit
   (habit-block ?b2 ?habit)
   [?b2 :block/refs ?p1]]
  [(pages-next-day ?p1 ?p2 ?habit)
   [?bx :block/page ?px]
   (habit-block ?bx ?habit)
   [?bx :block/refs ?p1]
   (pages-next-day ?px ?p2 ?habit)]
 ]
}

The next thing, I would like to find out is how to alter the :in and :inputs statement to simultaneously query for several habits at once. Using

:in $ ?today [?habit ...] %
:inputs [:today [:test :sports]]

gives the output of the longest running streak, but not two separate count outputs. The next one

:in $ [[?today ?habit] ...] %
:inputs [[:today :test] [:today :sports]]

gives an error: #error {:message "Extra inputs passed, expected: [$ [[?today ?habit] ...] %], got: 4", :data {:error :query/inputs, :expected [#datascript.parser.BindScalar{:variable #datascrip .... However, my basic question has been solved.